import { describe, it, expect, vi, beforeEach } from 'vitest';
import { PromptService } from '../src/services/prompt.service.js';
import type { IPromptRepository } from '../src/repositories/prompt.repository.js';
import type { IPromptRequestRepository } from '../src/repositories/prompt-request.repository.js';
import type { IProjectRepository } from '../src/repositories/project.repository.js';
import type { Prompt, PromptRequest, Project } from '@prisma/client';
import { ResourceRuleRegistry } from '../src/validation/resource-rules.js';
import { systemPromptVarsRule } from '../src/validation/rules/system-prompt-vars.js';
import {
  getSystemPromptNames,
  getSystemPromptRequiredVars,
  getSystemPromptDefault,
} from '../src/bootstrap/system-project.js';

function makePrompt(overrides: Partial<Prompt> = {}): Prompt {
  return {
    id: 'prompt-1',
    name: 'test-prompt',
    content: 'Hello world',
    projectId: null,
    priority: 5,
    summary: null,
    chapters: null,
    linkTarget: null,
    version: 1,
    createdAt: new Date(),
    updatedAt: new Date(),
    ...overrides,
  };
}

function makeProject(overrides: Partial<Project> = {}): Project {
  return {
    id: 'proj-1',
    name: 'test-project',
    description: '',
    prompt: '',
    proxyModel: '',
    gated: true,
    llmProvider: null,
    llmModel: null,
    ownerId: 'user-1',
    createdAt: new Date(),
    updatedAt: new Date(),
    ...overrides,
  } as Project;
}

function mockPromptRepo(): IPromptRepository {
  return {
    findAll: vi.fn(async () => []),
    findGlobal: vi.fn(async () => []),
    findById: vi.fn(async () => null),
    findByNameAndProject: vi.fn(async () => null),
    create: vi.fn(async (data) => makePrompt(data)),
    update: vi.fn(async (id, data) => makePrompt({ id, ...data })),
    delete: vi.fn(async () => {}),
  };
}

function mockPromptRequestRepo(): IPromptRequestRepository {
  return {
    findAll: vi.fn(async () => []),
    findGlobal: vi.fn(async () => []),
    findById: vi.fn(async () => null),
    findByNameAndProject: vi.fn(async () => null),
    findBySession: vi.fn(async () => []),
    create: vi.fn(async (data) => ({ id: 'req-1', ...data }) as PromptRequest),
    update: vi.fn(async (id, data) => ({ id, ...data }) as PromptRequest),
    delete: vi.fn(async () => {}),
  };
}

function mockProjectRepo(): IProjectRepository {
  return {
    findAll: vi.fn(async () => []),
    findById: vi.fn(async () => null),
    findByName: vi.fn(async () => null),
    create: vi.fn(async (data) => makeProject(data)),
    update: vi.fn(async (id, data) => makeProject({ id, ...data })),
    delete: vi.fn(async () => {}),
  };
}

describe('System Prompt Validation', () => {
  let promptRepo: IPromptRepository;
  let promptRequestRepo: IPromptRequestRepository;
  let projectRepo: IProjectRepository;
  let registry: ResourceRuleRegistry;
  let service: PromptService;

  const systemProject = makeProject({ id: 'sys-proj', name: 'mcpctl-system' });

  beforeEach(() => {
    promptRepo = mockPromptRepo();
    promptRequestRepo = mockPromptRequestRepo();
    projectRepo = mockProjectRepo();
    registry = new ResourceRuleRegistry();
    registry.register(systemPromptVarsRule);
    service = new PromptService(promptRepo, promptRequestRepo, projectRepo, registry);
  });

  describe('getSystemPromptNames', () => {
    it('includes all 11 system prompts (5 gate + 6 LLM)', () => {
      const names = getSystemPromptNames();
      expect(names).toContain('gate-instructions');
      expect(names).toContain('gate-encouragement');
      expect(names).toContain('gate-intercept-preamble');
      expect(names).toContain('gate-session-active');
      expect(names).toContain('session-greeting');
      expect(names).toContain('llm-response-filter');
      expect(names).toContain('llm-request-optimization');
      expect(names).toContain('llm-pagination-index');
      expect(names).toContain('llm-gate-context-selector');
      expect(names).toContain('llm-summarize');
      expect(names).toContain('llm-paginate-titles');
      expect(names.length).toBe(11);
    });
  });

  describe('getSystemPromptRequiredVars', () => {
    it('returns {{maxTokens}} for llm-summarize', () => {
      expect(getSystemPromptRequiredVars('llm-summarize')).toEqual(['{{maxTokens}}']);
    });

    it('returns {{pageCount}} for llm-paginate-titles', () => {
      expect(getSystemPromptRequiredVars('llm-paginate-titles')).toEqual(['{{pageCount}}']);
    });

    it('returns undefined for prompts without required vars', () => {
      expect(getSystemPromptRequiredVars('llm-response-filter')).toBeUndefined();
      expect(getSystemPromptRequiredVars('gate-instructions')).toBeUndefined();
    });

    it('returns undefined for unknown prompts', () => {
      expect(getSystemPromptRequiredVars('nonexistent')).toBeUndefined();
    });
  });

  describe('getSystemPromptDefault', () => {
    it('returns default content for each system prompt', () => {
      for (const name of getSystemPromptNames()) {
        const content = getSystemPromptDefault(name);
        expect(content).toBeDefined();
        expect(typeof content).toBe('string');
        expect(content!.length).toBeGreaterThan(0);
      }
    });

    it('returns undefined for unknown prompts', () => {
      expect(getSystemPromptDefault('nonexistent')).toBeUndefined();
    });
  });

  describe('updatePrompt validation', () => {
    it('rejects edits missing required {{maxTokens}} for llm-summarize', async () => {
      vi.mocked(promptRepo.findById).mockResolvedValue(
        makePrompt({ name: 'llm-summarize', projectId: 'sys-proj', content: 'old content with {{maxTokens}}' }),
      );
      vi.mocked(projectRepo.findById).mockResolvedValue(systemProject);

      await expect(
        service.updatePrompt('prompt-1', { content: 'Summarize this. No template vars here.' }),
      ).rejects.toThrow("requires template variable {{maxTokens}}");
    });

    it('rejects edits missing required {{pageCount}} for llm-paginate-titles', async () => {
      vi.mocked(promptRepo.findById).mockResolvedValue(
        makePrompt({ name: 'llm-paginate-titles', projectId: 'sys-proj', content: 'old with {{pageCount}}' }),
      );
      vi.mocked(projectRepo.findById).mockResolvedValue(systemProject);

      await expect(
        service.updatePrompt('prompt-1', { content: 'Generate titles for pages.' }),
      ).rejects.toThrow("requires template variable {{pageCount}}");
    });

    it('allows edits for prompts without required vars', async () => {
      vi.mocked(promptRepo.findById).mockResolvedValue(
        makePrompt({ name: 'llm-response-filter', projectId: 'sys-proj', content: 'old' }),
      );
      vi.mocked(projectRepo.findById).mockResolvedValue(systemProject);

      await expect(
        service.updatePrompt('prompt-1', { content: 'You are a new data filtering assistant.' }),
      ).resolves.toBeDefined();
    });

    it('allows edits that preserve all required vars', async () => {
      vi.mocked(promptRepo.findById).mockResolvedValue(
        makePrompt({ name: 'llm-summarize', projectId: 'sys-proj', content: 'old' }),
      );
      vi.mocked(projectRepo.findById).mockResolvedValue(systemProject);

      await expect(
        service.updatePrompt('prompt-1', { content: 'Custom summarize in {{maxTokens}} tokens.' }),
      ).resolves.toBeDefined();
    });

    it('allows edits to non-system project prompts without validation', async () => {
      vi.mocked(promptRepo.findById).mockResolvedValue(
        makePrompt({ name: 'some-prompt', projectId: 'proj-1', content: 'old' }),
      );
      vi.mocked(projectRepo.findById).mockResolvedValue(makeProject({ id: 'proj-1', name: 'my-project' }));

      await expect(
        service.updatePrompt('prompt-1', { content: 'anything goes' }),
      ).resolves.toBeDefined();
    });
  });

  describe('deletePrompt resets system prompts', () => {
    it('resets system prompt content to default instead of deleting', async () => {
      vi.mocked(promptRepo.findById).mockResolvedValue(
        makePrompt({ name: 'llm-response-filter', projectId: 'sys-proj', content: 'custom content' }),
      );
      vi.mocked(projectRepo.findById).mockResolvedValue(systemProject);

      const result = await service.deletePrompt('prompt-1');
      expect(result).toBeDefined();
      expect(promptRepo.update).toHaveBeenCalledWith('prompt-1', {
        content: getSystemPromptDefault('llm-response-filter'),
      });
      expect(promptRepo.delete).not.toHaveBeenCalled();
    });

    it('allows deletion of non-system project prompts', async () => {
      vi.mocked(promptRepo.findById).mockResolvedValue(
        makePrompt({ projectId: 'proj-1' }),
      );
      vi.mocked(projectRepo.findById).mockResolvedValue(makeProject({ id: 'proj-1', name: 'my-project' }));

      await service.deletePrompt('prompt-1');
      expect(promptRepo.delete).toHaveBeenCalledWith('prompt-1');
    });
  });
});
