import { describe, it, expect, beforeEach, vi } from 'vitest';
import Fastify, { FastifyInstance } from 'fastify';
import templateRoutes from './templates.js';
import type { SceneTemplate } from '../types/api.js';

function createMockTemplateManager() {
  return {
    listTemplates: vi.fn(),
    getTemplate: vi.fn(),
    fillTemplate: vi.fn(),
    initDefaults: vi.fn(),
  };
}

function createMockDataStore() {
  return {
    saveTemplate: vi.fn(),
    getTemplate: vi.fn(),
    listTemplates: vi.fn(),
    deleteTemplate: vi.fn(),
  };
}

const sampleTemplate: SceneTemplate = {
  id: 'beach-123',
  name: 'Beach',
  category: 'outdoor',
  promptTemplate: '{subject} on a beach, {camera}',
  environment: { lighting: 'golden hour', timeOfDay: 'sunset' },
  defaultAspectRatio: '16:9',
  createdAt: '2024-01-01T00:00:00.000Z',
  updatedAt: '2024-01-01T00:00:00.000Z',
};

describe('templateRoutes', () => {
  let app: FastifyInstance;
  let mockTmplManager: ReturnType<typeof createMockTemplateManager>;
  let mockDataStore: ReturnType<typeof createMockDataStore>;

  beforeEach(async () => {
    vi.clearAllMocks();
    mockTmplManager = createMockTemplateManager();
    mockDataStore = createMockDataStore();

    app = Fastify();
    await app.register(templateRoutes, {
      templateManager: mockTmplManager as any,
      dataStore: mockDataStore as any,
    });
    await app.ready();
  });

  describe('GET /templates', () => {
    it('should return all templates', async () => {
      mockTmplManager.listTemplates.mockResolvedValue([sampleTemplate]);

      const res = await app.inject({ method: 'GET', url: '/templates' });

      expect(res.statusCode).toBe(200);
      const body = res.json();
      expect(body.success).toBe(true);
      expect(body.data).toHaveLength(1);
      expect(body.data[0].name).toBe('Beach');
    });

    it('should filter by category query param', async () => {
      // Category filter now uses prompt-library getPromptsByCategory, not tmplManager
      const res = await app.inject({ method: 'GET', url: '/templates?category=outdoor' });

      expect(res.statusCode).toBe(200);
      expect(res.json().success).toBe(true);
    });

    it('should return empty array when no templates exist', async () => {
      mockTmplManager.listTemplates.mockResolvedValue([]);

      const res = await app.inject({ method: 'GET', url: '/templates' });

      expect(res.statusCode).toBe(200);
      expect(res.json().data).toEqual([]);
    });
  });

  describe('POST /templates', () => {
    it('should create a new template and return 201', async () => {
      mockDataStore.saveTemplate.mockResolvedValue(undefined);

      const res = await app.inject({
        method: 'POST',
        url: '/templates',
        payload: {
          name: 'Studio',
          category: 'professional',
          promptTemplate: '{subject} in studio, {camera}',
          environment: { lighting: 'studio', timeOfDay: 'daytime' },
          defaultAspectRatio: '3:4',
        },
      });

      expect(res.statusCode).toBe(201);
      const body = res.json();
      expect(body.success).toBe(true);
      expect(body.data.name).toBe('Studio');
      expect(body.data.category).toBe('professional');
      expect(body.data.id).toContain('studio-');
      expect(mockDataStore.saveTemplate).toHaveBeenCalledTimes(1);
    });

    it('should return 400 when required fields are missing', async () => {
      const res = await app.inject({
        method: 'POST',
        url: '/templates',
        payload: { name: 'Incomplete' },
      });

      expect(res.statusCode).toBe(400);
      expect(res.json().success).toBe(false);
    });
  });

  describe('GET /templates/:id', () => {
    it('should return template detail', async () => {
      mockDataStore.getTemplate.mockResolvedValue(sampleTemplate);

      const res = await app.inject({ method: 'GET', url: '/templates/beach-123' });

      expect(res.statusCode).toBe(200);
      const body = res.json();
      expect(body.success).toBe(true);
      expect(body.data.id).toBe('beach-123');
    });

    it('should return 404 when template not found', async () => {
      mockDataStore.getTemplate.mockResolvedValue(null);

      const res = await app.inject({ method: 'GET', url: '/templates/nonexistent' });

      expect(res.statusCode).toBe(404);
      expect(res.json().success).toBe(false);
    });
  });

  describe('PUT /templates/:id', () => {
    it('should update an existing template', async () => {
      mockDataStore.getTemplate.mockResolvedValue(sampleTemplate);
      mockDataStore.saveTemplate.mockResolvedValue(undefined);

      const res = await app.inject({
        method: 'PUT',
        url: '/templates/beach-123',
        payload: { name: 'Tropical Beach' },
      });

      expect(res.statusCode).toBe(200);
      const body = res.json();
      expect(body.success).toBe(true);
      expect(body.data.name).toBe('Tropical Beach');
      expect(body.data.category).toBe('outdoor'); // unchanged
    });

    it('should return 404 when template not found', async () => {
      mockDataStore.getTemplate.mockResolvedValue(null);

      const res = await app.inject({
        method: 'PUT',
        url: '/templates/nonexistent',
        payload: { name: 'Updated' },
      });

      expect(res.statusCode).toBe(404);
      expect(res.json().success).toBe(false);
    });
  });

  describe('DELETE /templates/:id', () => {
    it('should delete an existing template', async () => {
      mockDataStore.getTemplate.mockResolvedValue(sampleTemplate);
      mockDataStore.deleteTemplate.mockResolvedValue(true);

      const res = await app.inject({ method: 'DELETE', url: '/templates/beach-123' });

      expect(res.statusCode).toBe(200);
      const body = res.json();
      expect(body.success).toBe(true);
      expect(body.data.id).toBe('beach-123');
      expect(body.data.deleted).toBe(true);
    });

    it('should return 404 when template not found', async () => {
      mockDataStore.getTemplate.mockResolvedValue(null);

      const res = await app.inject({ method: 'DELETE', url: '/templates/nonexistent' });

      expect(res.statusCode).toBe(404);
      expect(res.json().success).toBe(false);
    });
  });
});
