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

// --- Mock data store ---
const store = new Map<string, SceneTemplate>();

vi.mock('../data-store/index.js', () => ({
  dataStore: {
    saveTemplate: vi.fn(async (t: SceneTemplate) => { store.set(t.id, structuredClone(t)); }),
    getTemplate: vi.fn(async (id: string) => {
      const v = store.get(id);
      return v ? structuredClone(v) : null;
    }),
    listTemplates: vi.fn(async () => Array.from(store.values()).map(v => structuredClone(v))),
    deleteTemplate: vi.fn(async (id: string) => store.delete(id)),
  },
}));

import { DEFAULT_TEMPLATES, TemplateManager, templateManager } from './templates.js';

let manager: TemplateManager;

beforeEach(() => {
  store.clear();
  manager = new TemplateManager();
});

describe('DEFAULT_TEMPLATES', () => {
  it('should have 12 predefined templates', () => {
    expect(DEFAULT_TEMPLATES).toHaveLength(12);
  });

  it('should include all required Turkish scene names', () => {
    const names = DEFAULT_TEMPLATES.map(t => t.name);
    for (const expected of [
      'Ev Ortamı', 'Yatak Odası', 'Sahil', 'Park Yürüyüşü',
      'Spor Salonu', 'Yoga Seansı', 'Kafe Molası', 'Restoran Akşamı',
      'Ofis', 'İş Toplantısı', 'Gece Kulübü', 'Rooftop Bar',
    ]) {
      expect(names).toContain(expected);
    }
  });

  it('should cover all 6 categories with at least 2 templates each', () => {
    const categoryCounts = new Map<string, number>();
    for (const t of DEFAULT_TEMPLATES) {
      categoryCounts.set(t.category, (categoryCounts.get(t.category) || 0) + 1);
    }
    for (const cat of ['indoor', 'outdoor', 'sport', 'social', 'professional', 'nightlife']) {
      expect(categoryCounts.get(cat)).toBeGreaterThanOrEqual(2);
    }
  });

  it('should have placeholder tokens in promptTemplate', () => {
    for (const t of DEFAULT_TEMPLATES) {
      expect(t.promptTemplate).toContain('{subject}');
      expect(t.promptTemplate).toContain('{clothing}');
      expect(t.promptTemplate).toContain('{camera}');
    }
  });

  it('should have English prompt templates', () => {
    for (const t of DEFAULT_TEMPLATES) {
      expect(t.promptTemplate).toMatch(/\{subject\}/);
    }
  });
});

describe('TemplateManager.initDefaults', () => {
  it('should save all default templates when store is empty', async () => {
    await manager.initDefaults();
    expect(store.size).toBe(12);
  });

  it('should not overwrite existing templates', async () => {
    const modified: SceneTemplate = { ...DEFAULT_TEMPLATES[0], name: 'Custom Template' } as SceneTemplate & { id: string; createdAt: string; updatedAt: string };
    const id = DEFAULT_TEMPLATES[0].name.toLowerCase().replace(/\s+/g, '-');
    const now = new Date().toISOString();
    const fullTemplate: SceneTemplate = { ...modified, id, createdAt: now, updatedAt: now };
    store.set(fullTemplate.id, fullTemplate);

    await manager.initDefaults();

    const saved = store.get(id)!;
    expect(saved.name).toBe('Custom Template');
    expect(store.size).toBe(1);
  });
});

describe('TemplateManager.getTemplate', () => {
  it('should return template by id', async () => {
    store.set('t1', { id: 't1', name: 'Test', category: 'indoor', promptTemplate: '{subject}', environment: { lighting: 'soft', timeOfDay: 'day' }, defaultAspectRatio: '1:1', createdAt: '', updatedAt: '' });
    const result = await manager.getTemplate('t1');
    expect(result).not.toBeNull();
    expect(result!.name).toBe('Test');
  });

  it('should return null for non-existent id', async () => {
    const result = await manager.getTemplate('nonexistent');
    expect(result).toBeNull();
  });
});

describe('TemplateManager.listTemplates', () => {
  beforeEach(async () => {
    await manager.initDefaults();
  });

  it('should return all templates when no category filter', async () => {
    const all = await manager.listTemplates();
    expect(all).toHaveLength(12);
  });

  it('should filter by category', async () => {
    const outdoor = await manager.listTemplates('outdoor');
    expect(outdoor.length).toBe(2);
    for (const t of outdoor) {
      expect(t.category).toBe('outdoor');
    }
  });

  it('should return empty array for non-matching category', async () => {
    const result = await manager.listTemplates('nonexistent' as any);
    expect(result).toHaveLength(0);
  });
});

describe('TemplateManager.createTemplate', () => {
  it('should create a template with generated id and timestamps', async () => {
    const created = await manager.createTemplate({
      name: 'Rooftop',
      category: 'social',
      promptTemplate: '{subject} on a rooftop, {clothing}, city skyline, {camera}',
      environment: { lighting: 'twilight', timeOfDay: 'evening' },
      defaultAspectRatio: '16:9',
    });

    expect(created.id).toBeTruthy();
    expect(created.createdAt).toBeTruthy();
    expect(created.updatedAt).toBeTruthy();
    expect(created.name).toBe('Rooftop');
    expect(store.has(created.id)).toBe(true);
  });
});

describe('TemplateManager.updateTemplate', () => {
  it('should update fields and set new updatedAt', async () => {
    await manager.initDefaults();
    const original = Array.from(store.values())[0];
    // Set updatedAt to a past time to ensure the update produces a different timestamp
    const pastTime = '2020-01-01T00:00:00.000Z';
    store.set(original.id, { ...original, updatedAt: pastTime });

    const updated = await manager.updateTemplate(original.id, { name: 'Updated Name' });

    expect(updated.name).toBe('Updated Name');
    expect(updated.id).toBe(original.id);
    expect(updated.createdAt).toBe(original.createdAt);
    expect(updated.updatedAt).not.toBe(pastTime);
  });

  it('should throw for non-existent template', async () => {
    await expect(manager.updateTemplate('nope', { name: 'X' })).rejects.toThrow('Template not found');
  });

  it('should not allow overriding id or createdAt', async () => {
    await manager.initDefaults();
    const original = Array.from(store.values())[0];

    const updated = await manager.updateTemplate(original.id, {
      id: 'hacked-id',
      createdAt: '1999-01-01T00:00:00Z',
    } as Partial<SceneTemplate>);

    expect(updated.id).toBe(original.id);
    expect(updated.createdAt).toBe(original.createdAt);
  });
});

describe('TemplateManager.deleteTemplate', () => {
  it('should delete an existing template', async () => {
    await manager.initDefaults();
    const firstId = Array.from(store.keys())[0];
    const result = await manager.deleteTemplate(firstId);
    expect(result).toBe(true);
    expect(store.has(firstId)).toBe(false);
  });
});

describe('TemplateManager.fillTemplate', () => {
  it('should replace all placeholders', () => {
    const template: SceneTemplate = {
      id: 't1', name: 'Test', category: 'outdoor',
      promptTemplate: '{subject}, at the beach, {clothing}, {camera}',
      environment: { lighting: 'sun', timeOfDay: 'day' },
      defaultAspectRatio: '1:1', createdAt: '', updatedAt: '',
    };

    const result = manager.fillTemplate(template, {
      subject: 'a young woman with brown hair',
      clothing: 'wearing a red dress',
      camera: 'shot on Canon R5 85mm f/1.4',
    });

    expect(result).toBe('a young woman with brown hair, at the beach, wearing a red dress, shot on Canon R5 85mm f/1.4');
  });

  it('should leave unmatched placeholders as-is', () => {
    const template: SceneTemplate = {
      id: 't1', name: 'Test', category: 'outdoor',
      promptTemplate: '{subject}, {clothing}, {unknown}',
      environment: { lighting: 'sun', timeOfDay: 'day' },
      defaultAspectRatio: '1:1', createdAt: '', updatedAt: '',
    };

    const result = manager.fillTemplate(template, { subject: 'person' });
    expect(result).toBe('person, {clothing}, {unknown}');
  });

  it('should handle empty vars', () => {
    const template: SceneTemplate = {
      id: 't1', name: 'Test', category: 'outdoor',
      promptTemplate: '{subject} at park',
      environment: { lighting: 'sun', timeOfDay: 'day' },
      defaultAspectRatio: '1:1', createdAt: '', updatedAt: '',
    };

    const result = manager.fillTemplate(template, {});
    expect(result).toBe('{subject} at park');
  });
});

describe('templateManager singleton', () => {
  it('should be an instance of TemplateManager', () => {
    expect(templateManager).toBeInstanceOf(TemplateManager);
  });
});
