import { describe, expect, it } from 'vitest';
import {
  buildImportPlan,
  CATEGORY_TRANSLATIONS,
  decodeHtmlEntities,
  isSameName,
  LEGACY_ENTRIES,
  normalizeName,
  slugify,
  translateCategoryName,
  uniqueSlug,
} from '../legacy/legacy-catalog.js';

describe('normalizeName', () => {
  it('trims and collapses whitespace', () => {
    expect(normalizeName('  hello   world  ')).toBe('hello world');
  });

  it('decodes HTML entities', () => {
    expect(normalizeName('NUTS &amp; SEEDS')).toBe('NUTS & SEEDS');
    expect(normalizeName('A &amp; B &nbsp; C')).toBe('A & B C');
  });

  it('keeps unknown entities as-is', () => {
    expect(normalizeName('foo &bogus; bar')).toBe('foo &bogus; bar');
  });
});

describe('decodeHtmlEntities', () => {
  it('decodes numeric entities', () => {
    expect(decodeHtmlEntities('&#38;')).toBe('&');
    expect(decodeHtmlEntities('&#x26;')).toBe('&');
  });
});

describe('isSameName', () => {
  it('compares case-insensitively after normalization', () => {
    expect(isSameName('nuez', 'NUEZ')).toBe(true);
    expect(isSameName('NUTS & SEEDS', 'nuts & seeds')).toBe(true);
    expect(isSameName('Hierbas', 'Hierba')).toBe(false);
  });
});

describe('slugify', () => {
  it('lowercases, strips diacritics and replaces non-alphanumerics', () => {
    expect(slugify('Frutas y Verduras')).toBe('frutas-y-verduras');
    expect(slugify('NUTS & SEEDS')).toBe('nuts-and-seeds');
    expect(slugify('A. Vogel')).toBe('a-vogel');
    expect(slugify('La Finestra Sul Cielo')).toBe('la-finestra-sul-cielo');
  });

  it('falls back to "entry" for empty input', () => {
    expect(slugify('---')).toBe('entry');
  });
});

describe('uniqueSlug', () => {
  it('returns the base when free', () => {
    expect(uniqueSlug('aceites', new Set(['frutos-secos']))).toBe('aceites');
  });

  it('appends a numeric suffix on collision', () => {
    expect(uniqueSlug('aceites', new Set(['aceites', 'aceites-2']))).toBe('aceites-3');
  });
});

describe('buildImportPlan', () => {
  it('classifies every legacy entry as category, brand, or exclude', () => {
    const kinds = new Set(LEGACY_ENTRIES.map((e) => e.kind));
    expect(kinds.has('category')).toBe(true);
    expect(kinds.has('brand')).toBe(true);
    expect(kinds.has('exclude')).toBe(true);
  });

  it('skips entries whose name already exists in the catalog (case-insensitive)', () => {
    const existingCategories = new Set(['frutos secos y semillas']);
    const existingBrands = new Set<string>();
    const existingSlugs = new Set<string>();
    const plan = buildImportPlan(existingCategories, existingBrands, existingSlugs);
    expect(plan.categories.find((c) => c.name === 'Frutos Secos y Semillas')).toBeUndefined();
  });

  it('skips excluded legacy markers', () => {
    const plan = buildImportPlan(new Set(), new Set(), new Set());
    expect(plan.categories.find((c) => c.name.startsWith('00 -'))).toBeUndefined();
    expect(plan.categories.find((c) => c.name.startsWith('01 -'))).toBeUndefined();
    expect(plan.categories.find((c) => c.name.startsWith('02 -'))).toBeUndefined();
  });

  it('produces unique slugs across categories and brands', () => {
    const plan = buildImportPlan(new Set(), new Set(), new Set());
    const slugs = [...plan.categories.map((c) => c.slug), ...plan.brands.map((b) => b.slug)];
    expect(new Set(slugs).size).toBe(slugs.length);
  });

  it('attaches the declared parent to each category', () => {
    const plan = buildImportPlan(new Set(), new Set(), new Set());
    const nuts = plan.categories.find((c) => c.name === 'Frutos Secos y Semillas');
    expect(nuts?.parentSlug).toBe('frutos-secos');
  });

  it('does not include brands as categories', () => {
    const plan = buildImportPlan(new Set(), new Set(), new Set());
    expect(plan.categories.find((c) => c.name === 'SOLGAR')).toBeUndefined();
    expect(plan.brands.find((b) => b.name === 'SOLGAR')).toBeDefined();
  });

  it('translates every legacy category name to Spanish Title Case', () => {
    const plan = buildImportPlan(new Set(), new Set(), new Set());
    const translatedNames = plan.categories.map((c) => c.name);
    // Spanish conjunctions/prepositions that stay lowercase in Title Case.
    const lowercase = new Set(['y', 'e', 'de', 'del', 'la', 'el', 'los', 'las', 'en', 'a', 'con']);
    for (const name of translatedNames) {
      const words = name.split(/\s+/).filter(Boolean);
      for (const word of words) {
        const firstChar = word.charAt(0);
        if (!/^[A-Za-zÁÉÍÓÚáéíóÚÑñ]/.test(firstChar)) continue;
        if (lowercase.has(word.toLowerCase())) {
          expect(firstChar).toBe(firstChar.toLowerCase());
        } else {
          expect(firstChar).toBe(firstChar.toUpperCase());
        }
      }
    }
  });

  it('covers every legacy category with a translation entry', () => {
    const legacyCategoryNames = LEGACY_ENTRIES
      .filter((e) => e.kind === 'category')
      .map((e) => e.name);
    for (const name of legacyCategoryNames) {
      expect(translateCategoryName(name)).not.toBe(name);
    }
  });

  it('translation table has one entry per legacy category', () => {
    const legacyCategoryNames = LEGACY_ENTRIES
      .filter((e) => e.kind === 'category')
      .map((e) => e.name);
    // Includes renamed entries and aliases already in Spanish.
    const known = new Set<string>();
    for (const key of Object.keys(CATEGORY_TRANSLATIONS)) known.add(key.toUpperCase());
    for (const name of legacyCategoryNames) {
      expect(known.has(name.toUpperCase())).toBe(true);
    }
  });
});
