import { describe, it, expect, beforeEach } from 'vitest';
import { CopyMeThatHtmlParser } from '../CopyMeThatHtmlParser.js';

const parser = new CopyMeThatHtmlParser();

describe('CopyMeThatHtmlParser', () => {
  describe('parseRecipeBlock', () => {
    it('parses a valid recipe block with all fields', () => {
      const html = `
        <div class="recipe">
          <div id="name">Chocolate Cake</div>
          <a id="original_link" href="https://example.com/cake">Source</a>
          <div id="description">Delicious cake</div>
          <img class="recipeImage" src="images/cake.jpg" />
          <span class="recipeCategory">Dessert</span>
          <span class="recipeCategory">Birthday</span>
          <span id="made_this">I made this</span>
          <span id="ratingValue">5</span>
          <a id="recipeYield">12 servings</a>
          <ul><li class="recipeIngredient">1 cup flour</li></ul>
          <ul><li class="instruction">Mix and bake</li></ul>
          <div id="recipeNotes"><div class="recipeNote">Note 1</div></div>
        </div>
      `;
      const result = parser.parseRecipeBlock(html);
      expect(result).not.toBeNull();
      expect(result!.title).toBe('Chocolate Cake');
      expect(result!.sourceUrl).toBe('https://example.com/cake');
      expect(result!.description).toBe('Delicious cake');
      expect(result!.imageUrl).toBe('images/cake.jpg');
      expect(result!.tags).toEqual(['Dessert', 'Birthday']);
      expect(result!.made).toBe(true);
      expect(result!.rating).toBe(5);
      expect(result!.servings).toBe('12 servings');
      expect(result!.ingredients).toEqual(['1 cup flour']);
      expect(result!.instructions).toEqual(['Mix and bake']);
      expect(result!.notes).toBe('Note 1');
    });

    it('returns null if title missing', () => {
      const html = `<div class="recipe"></div>`;
      expect(parser.parseRecipeBlock(html)).toBeNull();
    });

    it('returns null if no ingredients', () => {
      const html = `
        <div class="recipe">
          <div id="name">Test</div>
          <ul></ul>
          <ul><li class="instruction">Step</li></ul>
        </div>
      `;
      expect(parser.parseRecipeBlock(html)).toBeNull();
    });

    it('returns null if no steps', () => {
      const html = `
        <div class="recipe">
          <div id="name">Test</div>
          <ul><li class="recipeIngredient">ing</li></ul>
          <ul></ul>
        </div>
      `;
      expect(parser.parseRecipeBlock(html)).toBeNull();
    });

    it('handles missing optional fields gracefully', () => {
      const html = `
        <div class="recipe">
          <div id="name">Minimal</div>
          <ul><li class="recipeIngredient">ing</li></ul>
          <ul><li class="instruction">step</li></ul>
        </div>
      `;
      const result = parser.parseRecipeBlock(html);
      expect(result).not.toBeNull();
      expect(result!.sourceUrl).toBeUndefined();
      expect(result!.description).toBeUndefined();
      expect(result!.imageUrl).toBeUndefined();
      expect(result!.tags).toEqual([]);
      expect(result!.made).toBe(false);
      expect(result!.rating).toBeUndefined();
      expect(result!.servings).toBeUndefined();
      expect(result!.notes).toBeUndefined();
    });

    it('cleans HTML tags and entities in text', () => {
      const html = `
        <div class="recipe">
          <div id="name">Test &amp; &lt;tag&gt;</div>
          <ul><li class="recipeIngredient">ing &nbsp; extra</li></ul>
          <ul><li class="instruction">step with &quot;quotes&quot;</li></ul>
        </div>
      `;
      const result = parser.parseRecipeBlock(html);
      expect(result!.title).toBe('Test & <tag>');
      // cleanText collapses whitespace, so multiple spaces become single
      expect(result!.ingredients[0]).toBe('ing extra');
      expect(result!.instructions[0]).toBe('step with "quotes"');
    });

    it('extracts rating only if 1-5', () => {
      const html1 = `<div class="recipe"><div id="name">A</div><ul><li class="recipeIngredient">i</li></ul><ul><li class="instruction">s</li></ul><span id="ratingValue">3</span></div>`;
      expect(parser.parseRecipeBlock(html1)!.rating).toBe(3);
      const html2 = html1.replace('3', '0');
      expect(parser.parseRecipeBlock(html2)!.rating).toBeUndefined();
      const html3 = html1.replace('3', '6');
      expect(parser.parseRecipeBlock(html3)!.rating).toBeUndefined();
    });

    it('handles multiple notes', () => {
      const html = `
        <div class="recipe">
          <div id="name">Test</div>
          <ul><li class="recipeIngredient">ing</li></ul>
          <ul><li class="instruction">step</li></ul>
          <div id="recipeNotes">
            <div class="recipeNote">Note A</div>
            <div class="recipeNote">Note B</div>
          </div>
        </div>
      `;
      const result = parser.parseRecipeBlock(html);
      // cleanText collapses whitespace, so notes are space-separated
      expect(result!.notes).toBe('Note A Note B');
    });

    it('extracts servings and truncates', () => {
      const html = `
        <div class="recipe">
          <div id="name">Test</div>
          <ul><li class="recipeIngredient">ing</li></ul>
          <ul><li class="instruction">step</li></ul>
          <a id="recipeYield"> 8 servings </a>
        </div>
      `;
      const result = parser.parseRecipeBlock(html);
      expect(result!.servings).toBe('8 servings');
    });
  });

  describe('toCreateRecipeInput', () => {
    it('converts parsed recipe correctly', () => {
      const parsed = {
        title: 'Pizza',
        sourceUrl: 'https://example.com/pizza',
        description: 'Cheesy pizza',
        imageUrl: 'images/pizza.jpg',
        tags: ['Italian', 'Dinner'],
        made: true,
        rating: 4,
        servings: '4 servings',
        ingredients: ['dough', 'sauce', 'cheese'],
        instructions: ['Roll dough', 'Add toppings', 'Bake'],
        notes: 'Tips',
      };
      const input = parser.toCreateRecipeInput(parsed);
      expect(input.title).toBe('Pizza');
      expect(input.source_url).toBe('https://example.com/pizza');
      expect(input.description).toBe('Cheesy pizza');
      expect(input.image_url).toBe('/images/pizza.jpg'); // normalized
      expect(input.tagIds).toBeUndefined(); // tags handled separately in service
      expect(input.made).toBe(true);
      expect(input.rating).toBe(4);
      expect(input.servings).toBe(4);
      expect(input.ingredients).toHaveLength(3);
      expect(input.ingredients[0]).toEqual({ item: 'dough', position: 0 });
      expect(input.steps).toHaveLength(3);
      expect(input.steps[0]).toEqual({ instruction: 'Roll dough', position: 0 });
      expect(input.notes).toBe('Tips');
    });

    it('normalizes relative image path to /images/...', () => {
      const parsed = {
        imageUrl: 'images/foo.jpg',
        ingredients: [],
        instructions: [],
        title: 'X',
        tags: [],
        made: false,
      };
      const input = parser.toCreateRecipeInput(parsed);
      expect(input.image_url).toBe('/images/foo.jpg');
    });

    it('discards non-URL image paths', () => {
      const parsed = {
        imageUrl: 'some/relative/path.jpg',
        ingredients: [],
        instructions: [],
        title: 'X',
        tags: [],
        made: false,
      };
      const input = parser.toCreateRecipeInput(parsed);
      expect(input.image_url).toBeUndefined();
    });

    it('extracts serving count from string', () => {
      const parsed = {
        servings: '6 servings',
        ingredients: [],
        instructions: [],
        title: 'X',
        tags: [],
        made: false,
      };
      const input = parser.toCreateRecipeInput(parsed);
      expect(input.servings).toBe(6);
    });
  });

  describe('extractRecipeBlocks', () => {
    it('extracts all recipe blocks from multi-recipe HTML', () => {
      const html = `
        <html><body>
          <div class="recipe">...</div>
          <div class="recipe">...</div>
          <div class="recipe">...</div>
        </body></html>
      `;
      const blocks = parser.extractRecipeBlocks(html);
      expect(blocks.length).toBe(3);
    });

    it('handles whitespace around class attributes', () => {
      const html = `<div class = "recipe">A</div><div class='recipe'>B</div>`;
      const blocks = parser.extractRecipeBlocks(html);
      expect(blocks.length).toBe(2);
    });
  });
});
