import { query } from '../db.js';
import { MODEL_FAMILY_PATTERNS, GENDER_PATTERNS, ACTIVITY_PATTERNS } from './taxonomy.js';

export interface ProductInput {
  id: string;
  name: string;
  brand?: string;
  gender?: string;
  price?: number;
  original_price?: number;
  description?: string;
  feed_category?: string;
  shop?: string;
}

export interface CategorizationResult {
  productId: string;
  categories: Array<{ slug: string; dimension: string; isPrimary: boolean; confidence: number }>;
  tags: string[];
  detectedGender: string | null;
  detectedBrand: string | null;
  detectedActivity: string | null;
}

/** Normalize product name for matching */
export function normalize(name: string): string {
  return name
    .toLowerCase()
    .replace(/[''`]/g, '')
    .replace(/[-_\/\\]/g, ' ')
    .replace(/\s+/g, ' ')
    .replace(/\bwmns\b/g, 'women')
    .replace(/\bgs\b/g, 'grade school')
    .replace(/\btd\b/g, 'toddler')
    .replace(/\bps\b/g, 'preschool')
    .trim();
}

/** Detect brand from product name or existing brand field */
export function detectBrand(product: ProductInput): string | null {
  if (product.brand) return product.brand.toLowerCase();

  const name = product.name.toLowerCase();
  const brands = [
    'nike', 'adidas', 'new balance', 'puma', 'converse', 'vans',
    'asics', 'reebok', 'on running', 'salomon', 'jordan',
  ];
  for (const brand of brands) {
    if (name.includes(brand)) return brand === 'jordan' ? 'nike' : brand;
  }
  return null;
}

/** Detect gender from product name */
export function detectGender(normalized: string, feedGender?: string): string | null {
  for (const { patterns, gender } of GENDER_PATTERNS) {
    for (const pattern of patterns) {
      if (pattern.test(normalized)) return gender;
    }
  }
  // Fallback to feed gender field
  if (feedGender) {
    const g = feedGender.toLowerCase();
    if (['heren', 'men', 'male', 'man'].some(k => g.includes(k))) return 'men';
    if (['dames', 'women', 'female', 'vrouw'].some(k => g.includes(k))) return 'women';
    if (['unisex'].some(k => g.includes(k))) return 'unisex';
    if (['kids', 'kinderen', 'junior'].some(k => g.includes(k))) return 'kids';
  }
  return null;
}

/** Detect model family from brand + product name */
export function detectModelFamily(brand: string, normalized: string): {
  slug: string;
  activity?: string;
  tags?: string[];
} | null {
  const brandPatterns = MODEL_FAMILY_PATTERNS[brand];
  if (!brandPatterns) return null;

  for (const family of brandPatterns) {
    for (const pattern of family.patterns) {
      if (pattern.test(normalized)) {
        return { slug: family.slug, activity: family.activity, tags: family.tags };
      }
    }
  }
  return null;
}

/** Detect activity category from keywords */
export function detectActivity(normalized: string, feedCategory?: string): string | null {
  const text = `${normalized} ${feedCategory || ''}`.toLowerCase();
  for (const { patterns, slug } of ACTIVITY_PATTERNS) {
    for (const pattern of patterns) {
      if (pattern.test(text)) return slug;
    }
  }
  return null;
}

/** Categorize a single product */
export function categorizeProduct(product: ProductInput): CategorizationResult {
  const normalized = normalize(product.name);
  const result: CategorizationResult = {
    productId: product.id,
    categories: [],
    tags: [],
    detectedGender: null,
    detectedBrand: null,
    detectedActivity: null,
  };

  // 1. Brand detection
  const brand = detectBrand(product);
  result.detectedBrand = brand;
  if (brand) {
    // Map brand name to brand_family slug
    const brandSlugMap: Record<string, string> = {
      'nike': 'nike', 'adidas': 'adidas', 'new balance': 'new-balance',
      'puma': 'puma', 'converse': 'converse', 'vans': 'vans',
      'asics': 'asics', 'reebok': 'reebok', 'on running': 'on-running',
      'salomon': 'salomon',
    };
    const brandSlug = brandSlugMap[brand];
    if (brandSlug) {
      result.categories.push({ slug: brandSlug, dimension: 'brand_family', isPrimary: true, confidence: 1.0 });
    }
  }

  // 2. Gender detection
  const gender = detectGender(normalized, product.gender);
  result.detectedGender = gender;
  if (gender) {
    result.categories.push({ slug: gender, dimension: 'gender', isPrimary: true, confidence: 1.0 });
  }

  // 3. Model family detection
  if (brand) {
    const family = detectModelFamily(brand, normalized);
    if (family) {
      result.categories.push({ slug: family.slug, dimension: 'brand_family', isPrimary: true, confidence: 1.0 });
      if (family.activity) {
        result.detectedActivity = family.activity;
        result.categories.push({ slug: family.activity, dimension: 'activity', isPrimary: true, confidence: 0.9 });
      }
      if (family.tags) {
        result.tags.push(...family.tags);
      }
    }
  }

  // 4. Activity detection (fallback if not set by model family)
  if (!result.detectedActivity) {
    const activity = detectActivity(normalized, product.feed_category);
    if (activity) {
      result.detectedActivity = activity;
      result.categories.push({ slug: activity, dimension: 'activity', isPrimary: true, confidence: 0.8 });
    } else {
      // Default to lifestyle if no activity detected
      result.detectedActivity = 'lifestyle';
      result.categories.push({ slug: 'lifestyle', dimension: 'activity', isPrimary: true, confidence: 0.5 });
    }
  }

  return result;
}

/** Write categorization results to DB */
export async function writeCategorizationResults(results: CategorizationResult[]): Promise<number> {
  let written = 0;

  for (const result of results) {
    // Deduplicate categories by slug (keep highest confidence)
    const catMap = new Map<string, typeof result.categories[0]>();
    for (const cat of result.categories) {
      const existing = catMap.get(cat.slug);
      if (!existing || cat.confidence > existing.confidence) {
        catMap.set(cat.slug, cat);
      }
    }

    for (const cat of catMap.values()) {
      try {
        await query(
          `INSERT INTO product_categories (product_id, category_id, is_primary, confidence, source)
           SELECT $1, c.id, $3, $4, 'rule'
           FROM categories c WHERE c.slug = $2
           ON CONFLICT (product_id, category_id) DO UPDATE
           SET confidence = GREATEST(product_categories.confidence, EXCLUDED.confidence),
               source = CASE WHEN product_categories.source = 'manual' THEN 'manual' ELSE EXCLUDED.source END`,
          [result.productId, cat.slug, cat.isPrimary, cat.confidence]
        );
        written++;
      } catch (e) {
        // Category slug might not exist, skip
      }
    }

    // Write tags
    const uniqueTags = [...new Set(result.tags)];
    for (const tagSlug of uniqueTags) {
      try {
        await query(
          `INSERT INTO product_tags (product_id, tag_id, source)
           SELECT $1, t.id, 'rule'
           FROM tags t WHERE t.slug = $2
           ON CONFLICT (product_id, tag_id) DO NOTHING`,
          [result.productId, tagSlug]
        );
      } catch (e) {
        // Tag might not exist, skip
      }
    }

    // Update product metadata
    await query(
      `UPDATE products SET
        categorized_at = now(),
        categorization_version = categorization_version + 1,
        gender = COALESCE($2, gender),
        brand = COALESCE($3, brand)
       WHERE id = $1`,
      [result.productId, result.detectedGender, result.detectedBrand]
    );
  }

  return written;
}

/** Run auto-categorization on products */
export async function autoCategorize(options: { all?: boolean } = {}): Promise<{
  processed: number;
  categorized: number;
}> {
  const whereClause = options.all
    ? "WHERE 1=1"
    : "WHERE categorized_at IS NULL OR categorization_version = 0";

  // Don't re-categorize manually categorized products
  const { rows: products } = await query<ProductInput>(
    `SELECT p.id, p.name, p.brand, p.gender, p.price, p.original_price
     FROM products p
     ${whereClause}
     AND NOT EXISTS (
       SELECT 1 FROM product_categories pc
       WHERE pc.product_id = p.id AND pc.source = 'manual'
     )
     ORDER BY p.created_at DESC`
  );

  const results: CategorizationResult[] = [];
  for (const product of products) {
    results.push(categorizeProduct(product));
  }

  const written = await writeCategorizationResults(results);

  return { processed: products.length, categorized: written };
}
