import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import express, { type Express } from 'express';
import initSqlJs from 'sql.js';
import { createTagRoutes } from '../routes/tags.js';
import { createRecipeRoutes } from '../routes/recipes.js';
import fs from 'fs';
import path from 'path';

describe('Tag API', () => {
  let app: Express;
  let db: any;

  beforeEach(async () => {
    const SQL = await initSqlJs();
    db = new SQL.Database();
    const schemaPath = path.join(process.cwd(), 'src/backend/db/schema.sql');
    const schema = fs.readFileSync(schemaPath, 'utf-8');
    db.exec(schema);
    // Enable foreign key constraints for tests
    db.run('PRAGMA foreign_keys = ON');
    app = express();
    app.use(express.json());
    app.use('/api/tags', createTagRoutes(db));
    app.use('/api/recipes', createRecipeRoutes(db));
    // Create test recipe for tag assignment tests
    db.run('INSERT INTO recipes (title, created_at, updated_at) VALUES (?, ?, ?)', ['Test Recipe', Date.now(), Date.now()]);
  });

  describe('POST /api/tags', () => {
    it('should create a new tag', async () => {
      const response = await request(app)
        .post('/api/tags')
        .send({ name: 'Breakfast' })
        .expect(201);
      expect(response.body.success).toBe(true);
      expect(response.body.data).toMatchObject({ name: 'Breakfast' });
      expect(response.body.data.id).toBeDefined();
    });
    it('should reject empty name', async () => {
      const response = await request(app)
        .post('/api/tags')
        .send({ name: '' })
        .expect(400);
      expect(response.body.success).toBe(false);
    });
    it('should reject duplicate tag names', async () => {
      await request(app).post('/api/tags').send({ name: 'Breakfast' }).expect(201);
      const response = await request(app).post('/api/tags').send({ name: 'Breakfast' }).expect(400);
      expect(response.body.success).toBe(false);
    });
  });

  describe('GET /api/tags', () => {
    it('should list all tags', async () => {
      await request(app).post('/api/tags').send({ name: 'Breakfast' });
      await request(app).post('/api/tags').send({ name: 'Lunch' });
      await request(app).post('/api/tags').send({ name: 'Dinner' });
      const response = await request(app).get('/api/tags').expect(200);
      expect(response.body.success).toBe(true);
      expect(response.body.data).toHaveLength(3);
    });
  });

  describe('PUT /api/tags/:id', () => {
    let tagId: number;
    beforeEach(async () => {
      const res = await request(app).post('/api/tags').send({ name: 'Old Name' });
      tagId = res.body.data.id;
    });

    it('should update tag name', async () => {
      const response = await request(app)
        .put(`/api/tags/${tagId}`)
        .send({ name: 'New Name' })
        .expect(200);
      expect(response.body.success).toBe(true);
      expect(response.body.data.name).toBe('New Name');
    });

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

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

  describe('DELETE /api/tags/:id', () => {
    let tagId: number;
    beforeEach(async () => {
      const res = await request(app).post('/api/tags').send({ name: 'Delete Me' });
      tagId = res.body.data.id;
    });

    it('should delete a tag', async () => {
      const response = await request(app).delete(`/api/tags/${tagId}`).expect(200);
      expect(response.body.success).toBe(true);
      expect(response.body.data).toBe(true);
      // Verify deletion
      const getRes = await request(app).get(`/api/tags`).expect(200);
      expect(getRes.body.data.find((t: any) => t.id === tagId)).toBeUndefined();
    });

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

  describe('Tag assignment to recipes', () => {
    let recipeId: number;
    let tagId: number;
    beforeEach(async () => {
      // Create recipe via API to get ID
      const recipeRes = await request(app)
        .post('/api/recipes')
        .send({
          title: 'Recipe for tagging',
          ingredients: [{ item: 'a' }],
          steps: [{ instruction: 'b' }],
        });
      recipeId = recipeRes.body.data.id;
      const tagRes = await request(app).post('/api/tags').send({ name: 'TestTag' });
      tagId = tagRes.body.data.id;
    });

    describe('POST /api/tags/:recipeId/assign', () => {
      it('should assign a tag to a recipe', async () => {
        const response = await request(app)
          .post(`/api/tags/${recipeId}/assign`)
          .send({ tag_id: tagId })
          .expect(200);
        expect(response.body.success).toBe(true);
        const recipe = await request(app).get(`/api/recipes/${recipeId}`);
        expect(recipe.body.data.tags.some((t: any) => t.id === tagId)).toBe(true);
      });

      it('should fail gracefully for non-existent recipe', async () => {
        const response = await request(app)
          .post('/api/tags/9999/assign')
          .send({ tag_id: tagId });
        // Assignment should fail due to FK constraint, but return 200 with success:false
        expect(response.status).toBe(200);
        expect(response.body.success).toBe(false);
      });
    });

    describe('POST /api/tags/:recipeId/remove', () => {
      beforeEach(async () => {
        // assign first
        await request(app).post(`/api/tags/${recipeId}/assign`).send({ tag_id: tagId });
      });

      it('should remove tag from recipe', async () => {
        const response = await request(app)
          .post(`/api/tags/${recipeId}/remove`)
          .send({ tag_id: tagId })
          .expect(200);
        expect(response.body.success).toBe(true);
        const recipe = await request(app).get(`/api/recipes/${recipeId}`);
        expect(recipe.body.data.tags.some((t: any) => t.id === tagId)).toBe(false);
      });

      it('should handle non-existent assignment gracefully', async () => {
        const response = await request(app)
          .post(`/api/tags/${recipeId}/remove`)
          .send({ tag_id: 9999 });
        // No rows removed, but returns 200 with success:false
        expect(response.status).toBe(200);
        expect(response.body.success).toBe(false);
      });
    });
  });
});
