import { describe, it, expect, beforeEach } from 'vitest';
import express from 'express';
import request from 'supertest';
import initSqlJs from 'sql.js';
import { readFileSync } from 'fs';
import { createRecipeRoutes } from '../routes/recipes.js';
import { Tag } from '../types/tag.js';
import { Ingredient, Step } from '../types/recipe.js';

describe('Recipe API', () => {
  let app: express.Application;
  let db: any;
  beforeEach(async () => {
    const SQL = await initSqlJs();
    db = new SQL.Database();
    const schemaPath = new URL('../db/schema.sql', import.meta.url).pathname;
    const schema = readFileSync(schemaPath, 'utf-8');
    db.exec(schema);
    // Seed tags
    db.run("INSERT INTO tags (id, name) VALUES (1, 'Dessert'), (2, 'Breakfast')");
    app = express();
    app.use(express.json());
    app.use('/api/recipes', createRecipeRoutes(db));
  });

  describe('POST /api/recipes', () => {
    it('should create a new recipe (normalized)', async () => {
      const recipe = {
        title: 'Chocolate Chip Cookies',
        description: 'Classic homemade cookies',
        servings: 24,
        image_url: 'https://images.unsplash.com/photo-1499636136210-6f4ee915583e',
        ingredients: [{ item: 'flour', quantity: '2', unit: 'cups' }, { item: 'sugar' }, { item: 'chocolate chips' }],
        steps: [ { instruction: 'Mix ingredients' }, { instruction: 'Bake at 350°F' } ],
        tagIds: [1]
      };
      const response = await request(app)
        .post('/api/recipes')
        .send(recipe)
        .expect(201);
      expect(response.body.success).toBe(true);
      expect(response.body.data).toMatchObject({
        id: 1,
        title: recipe.title,
        description: recipe.description,
        servings: recipe.servings,
        image_url: recipe.image_url,
      });
      expect(response.body.data.ingredients[0]).toMatchObject({ item: 'flour' });
      expect(response.body.data.steps[0].instruction).toBe('Mix ingredients');
      expect(response.body.data.created_at).toBeDefined();
      expect(response.body.data.updated_at).toBeDefined();
      expect(response.body.data.tags).toEqual([{id:1,name:'Dessert'}]);
    });

    it('should reject recipe without title', async () => {
      const recipe = {
        ingredients: [{ item: 'flour' }],
        steps: [{ instruction: 'Mix' }],
      };
      const response = await request(app)
        .post('/api/recipes')
        .send(recipe)
        .expect(400);
      expect(response.body.success).toBe(false);
    });
  });

  describe('GET /api/recipes', () => {
    beforeEach(() => {
      db.run("INSERT INTO recipes (id, title, created_at, updated_at) VALUES (1, 'Chocolate Cake', 1, 1)");
      db.run("INSERT INTO recipes (id, title, created_at, updated_at) VALUES (2, 'Scrambled Eggs', 2, 2)");
      db.run("INSERT INTO recipes (id, title, created_at, updated_at) VALUES (3, 'BLT Sandwich', 3, 3)");
      db.run("INSERT INTO ingredients (recipe_id, item, position) VALUES (1, 'chocolate', 0)");
      db.run("INSERT INTO ingredients (recipe_id, item, position) VALUES (2, 'eggs', 0)");
      db.run("INSERT INTO ingredients (recipe_id, item, position) VALUES (3, 'bacon', 0)");
      db.run("INSERT INTO recipe_tags (recipe_id, tag_id) VALUES (1, 1)"); // Dessert
      db.run("INSERT INTO recipe_tags (recipe_id, tag_id) VALUES (2, 2)"); // Breakfast
      db.run("INSERT INTO recipe_tags (recipe_id, tag_id) VALUES (3, 2)"); // Breakfast
    });

    it('should search by recipe title', async () => {
      const res = await request(app).get('/api/recipes?search=Eggs').expect(200);
      expect(res.body.data.length).toBe(1);
      expect(res.body.data[0].title).toMatch(/Eggs/);
    });

    it('should search by ingredient item', async () => {
      const res = await request(app).get('/api/recipes?search=chocolate').expect(200);
      expect(res.body.data.length).toBe(1);
      expect(res.body.data[0].title).toMatch(/Chocolate/);
    });

    it('should search by tag name', async () => {
      const res = await request(app).get('/api/recipes?search=Dessert').expect(200);
      expect(res.body.data.length).toBe(1);
      expect(res.body.data[0].title).toMatch(/Chocolate/);
    });

    it('should filter by tag id', async () => {
      const res = await request(app).get('/api/recipes?tagId=2').expect(200);
      expect(res.body.data.length).toBe(2);
      const titles = res.body.data.map((r: any) => r.title);
      expect(titles).toContain('Scrambled Eggs');
      expect(titles).toContain('BLT Sandwich');
    });

    it('should filter by search AND tagId', async () => {
      const res = await request(app).get('/api/recipes?search=Sandwich&tagId=2').expect(200);
      expect(res.body.data.length).toBe(1);
      expect(res.body.data[0].title).toBe('BLT Sandwich');
    });
  });

  describe('PUT /api/recipes/:id', () => {
    let recipeId: number;
    beforeEach(async () => {
      // Create a recipe to update
      const res = await request(app)
        .post('/api/recipes')
        .send({
          title: 'Original Title',
          description: 'Original desc',
          ingredients: [{ item: 'ing1' }],
          steps: [{ instruction: 'step 1' }],
        });
      recipeId = res.body.data.id;
    });

    it('should update recipe fields', async () => {
      const response = await request(app)
        .put(`/api/recipes/${recipeId}`)
        .send({
          title: 'Updated Title',
          description: 'Updated desc',
        })
        .expect(200);
      expect(response.body.success).toBe(true);
      expect(response.body.data.title).toBe('Updated Title');
      expect(response.body.data.description).toBe('Updated desc');
      expect(response.body.data.updated_at).toBeDefined();
    });

    it('should replace ingredients and steps', async () => {
      const response = await request(app)
        .put(`/api/recipes/${recipeId}`)
        .send({
          ingredients: [{ item: 'new ing', quantity: '2', unit: 'cup' }],
          steps: [{ instruction: 'new step' }],
        })
        .expect(200);
      expect(response.body.data.ingredients).toHaveLength(1);
      expect(response.body.data.ingredients[0].item).toBe('new ing');
      expect(response.body.data.steps[0].instruction).toBe('new step');
    });

    it('should return 404 for non-existent recipe', async () => {
      const response = await request(app)
        .put('/api/recipes/9999')
        .send({ title: 'no' })
        .expect(404);
      expect(response.body.success).toBe(false);
    });

    it('should reject invalid updates (empty title)', async () => {
      const response = await request(app)
        .put(`/api/recipes/${recipeId}`)
        .send({ title: '' })
        .expect(400);
      expect(response.body.success).toBe(false);
    });
  });

  describe('DELETE /api/recipes/:id', () => {
    let recipeId: number;
    beforeEach(async () => {
      const res = await request(app)
        .post('/api/recipes')
        .send({
          title: 'To Delete',
          ingredients: [{ item: 'x' }],
          steps: [{ instruction: 'y' }],
        });
      recipeId = res.body.data.id;
    });

    it('should delete a recipe', async () => {
      const response = await request(app)
        .delete(`/api/recipes/${recipeId}`)
        .expect(200);
      expect(response.body.success).toBe(true);
      expect(response.body.data).toBe(true);
      // Verify deletion
      const getRes = await request(app).get(`/api/recipes/${recipeId}`).expect(404);
      expect(getRes.body.success).toBe(false);
    });

    it('should return 404 for non-existent recipe', async () => {
      const response = await request(app)
        .delete('/api/recipes/9999')
        .expect(404);
      expect(response.body.success).toBe(false);
    });
  });
});
