import type { Prompt, PromptRequest } from '@prisma/client';
import type { IPromptRepository } from '../repositories/prompt.repository.js';
import type { IPromptRequestRepository } from '../repositories/prompt-request.repository.js';
import type { IProjectRepository } from '../repositories/project.repository.js';
import { CreatePromptSchema, UpdatePromptSchema, CreatePromptRequestSchema, UpdatePromptRequestSchema } from '../validation/prompt.schema.js';
import { NotFoundError } from './mcp-server.service.js';
import type { PromptSummaryService } from './prompt-summary.service.js';
import { SYSTEM_PROJECT_NAME, getSystemPromptDefault } from '../bootstrap/system-project.js';
import type { ResourceRuleRegistry, RuleContext } from '../validation/resource-rules.js';

export class PromptService {
  private summaryService: PromptSummaryService | null = null;

  constructor(
    private readonly promptRepo: IPromptRepository,
    private readonly promptRequestRepo: IPromptRequestRepository,
    private readonly projectRepo: IProjectRepository,
    private readonly ruleRegistry?: ResourceRuleRegistry,
  ) {}

  setSummaryService(service: PromptSummaryService): void {
    this.summaryService = service;
  }

  /**
   * Run resource validation rules for a prompt.
   * Throws 400 if validation fails.
   */
  private async validatePromptRules(
    name: string,
    content: string,
    projectId: string | undefined | null,
    operation: 'create' | 'update',
  ): Promise<void> {
    if (!this.ruleRegistry || !projectId) return;

    const project = await this.projectRepo.findById(projectId);
    const isSystem = project?.name === SYSTEM_PROJECT_NAME;

    const ctx: RuleContext = {
      findResource: async () => null,
    };

    const result = await this.ruleRegistry.validate(
      'prompt',
      { name, content },
      { projectName: project?.name, isSystemResource: isSystem, operation },
      ctx,
    );

    if (!result.valid) {
      throw Object.assign(
        new Error(result.errors?.join('; ') ?? 'Validation failed'),
        { statusCode: 400 },
      );
    }
  }

  // ── Prompt CRUD ──

  async listPrompts(projectId?: string): Promise<Prompt[]> {
    return this.promptRepo.findAll(projectId);
  }

  async listGlobalPrompts(): Promise<Prompt[]> {
    return this.promptRepo.findGlobal();
  }

  async getPrompt(id: string): Promise<Prompt> {
    const prompt = await this.promptRepo.findById(id);
    if (prompt === null) throw new NotFoundError(`Prompt not found: ${id}`);
    return prompt;
  }

  async createPrompt(input: unknown): Promise<Prompt> {
    const data = CreatePromptSchema.parse(input);

    if (data.projectId) {
      const project = await this.projectRepo.findById(data.projectId);
      if (project === null) throw new NotFoundError(`Project not found: ${data.projectId}`);
    }

    await this.validatePromptRules(data.name, data.content, data.projectId, 'create');

    const createData: { name: string; content: string; projectId?: string; priority?: number; linkTarget?: string } = {
      name: data.name,
      content: data.content,
    };
    if (data.projectId !== undefined) createData.projectId = data.projectId;
    if (data.priority !== undefined) createData.priority = data.priority;
    if (data.linkTarget !== undefined) createData.linkTarget = data.linkTarget;
    const prompt = await this.promptRepo.create(createData);
    // Auto-generate summary/chapters (non-blocking — don't fail create if summary fails)
    if (this.summaryService && !data.linkTarget) {
      this.generateAndStoreSummary(prompt.id, data.content).catch(() => {});
    }
    return prompt;
  }

  async updatePrompt(id: string, input: unknown): Promise<Prompt> {
    const data = UpdatePromptSchema.parse(input);
    const existing = await this.getPrompt(id);

    if (data.content !== undefined) {
      await this.validatePromptRules(existing.name, data.content, existing.projectId, 'update');
    }

    const updateData: { content?: string; priority?: number } = {};
    if (data.content !== undefined) updateData.content = data.content;
    if (data.priority !== undefined) updateData.priority = data.priority;
    const prompt = await this.promptRepo.update(id, updateData);
    // Regenerate summary when content changes
    if (this.summaryService && data.content !== undefined && !prompt.linkTarget) {
      this.generateAndStoreSummary(prompt.id, data.content).catch(() => {});
    }
    return prompt;
  }

  async regenerateSummary(id: string): Promise<Prompt> {
    const prompt = await this.getPrompt(id);
    if (!this.summaryService) {
      throw new Error('Summary generation not available');
    }
    return this.generateAndStoreSummary(prompt.id, prompt.content);
  }

  private async generateAndStoreSummary(id: string, content: string): Promise<Prompt> {
    if (!this.summaryService) throw new Error('No summary service');
    const { summary, chapters } = await this.summaryService.generateSummary(content);
    return this.promptRepo.update(id, { summary, chapters });
  }

  async deletePrompt(id: string): Promise<Prompt | void> {
    const prompt = await this.getPrompt(id);
    // System prompts: reset to codebase default instead of deleting
    if (prompt.projectId) {
      const project = await this.projectRepo.findById(prompt.projectId);
      if (project?.name === SYSTEM_PROJECT_NAME) {
        const defaultContent = getSystemPromptDefault(prompt.name);
        if (defaultContent !== undefined) {
          return this.promptRepo.update(id, { content: defaultContent });
        }
        // Unknown system prompt — allow deletion
      }
    }
    await this.promptRepo.delete(id);
  }

  // ── PromptRequest CRUD ──

  async listPromptRequests(projectId?: string): Promise<PromptRequest[]> {
    return this.promptRequestRepo.findAll(projectId);
  }

  async listGlobalPromptRequests(): Promise<PromptRequest[]> {
    return this.promptRequestRepo.findGlobal();
  }

  async getPromptRequest(id: string): Promise<PromptRequest> {
    const req = await this.promptRequestRepo.findById(id);
    if (req === null) throw new NotFoundError(`PromptRequest not found: ${id}`);
    return req;
  }

  async updatePromptRequest(id: string, input: unknown): Promise<PromptRequest> {
    await this.getPromptRequest(id);
    const data = UpdatePromptRequestSchema.parse(input);
    const updateData: { content?: string; priority?: number } = {};
    if (data.content !== undefined) updateData.content = data.content;
    if (data.priority !== undefined) updateData.priority = data.priority;
    return this.promptRequestRepo.update(id, updateData);
  }

  async deletePromptRequest(id: string): Promise<void> {
    await this.getPromptRequest(id);
    await this.promptRequestRepo.delete(id);
  }

  // ── Propose (LLM creates a PromptRequest) ──

  async propose(input: unknown): Promise<PromptRequest> {
    const data = CreatePromptRequestSchema.parse(input);

    if (data.projectId) {
      const project = await this.projectRepo.findById(data.projectId);
      if (project === null) throw new NotFoundError(`Project not found: ${data.projectId}`);
    }

    const createData: { name: string; content: string; projectId?: string; priority?: number; createdBySession?: string; createdByUserId?: string } = {
      name: data.name,
      content: data.content,
    };
    if (data.projectId !== undefined) createData.projectId = data.projectId;
    if (data.priority !== undefined) createData.priority = data.priority;
    if (data.createdBySession !== undefined) createData.createdBySession = data.createdBySession;
    if (data.createdByUserId !== undefined) createData.createdByUserId = data.createdByUserId;
    return this.promptRequestRepo.create(createData);
  }

  // ── Approve (delete PromptRequest → create Prompt) ──

  async approve(requestId: string): Promise<Prompt> {
    const req = await this.getPromptRequest(requestId);

    // Create the approved prompt (carry priority from request)
    const createData: { name: string; content: string; projectId?: string; priority?: number } = {
      name: req.name,
      content: req.content,
    };
    if (req.projectId !== null) createData.projectId = req.projectId;
    if (req.priority !== 5) createData.priority = req.priority;

    const prompt = await this.promptRepo.create(createData);

    // Delete the request
    await this.promptRequestRepo.delete(requestId);

    return prompt;
  }

  // ── Backup/restore helpers ──

  async upsertByName(data: Record<string, unknown>): Promise<Prompt> {
    const name = data['name'] as string;
    let projectId: string | null = null;

    // Resolve project name to ID if provided
    if (data['project'] !== undefined) {
      const project = await this.projectRepo.findByName(data['project'] as string);
      if (project === null) throw new NotFoundError(`Project not found: ${data['project']}`);
      projectId = project.id;
    } else if (data['projectId'] !== undefined) {
      projectId = data['projectId'] as string;
    }

    const existing = await this.promptRepo.findByNameAndProject(name, projectId);

    if (existing !== null) {
      const updateData: { content?: string; priority?: number } = {};
      if (data['content'] !== undefined) updateData.content = data['content'] as string;
      if (data['priority'] !== undefined) updateData.priority = data['priority'] as number;
      if (Object.keys(updateData).length > 0) {
        return this.promptRepo.update(existing.id, updateData);
      }
      return existing;
    }

    const createData: { name: string; content: string; projectId?: string; priority?: number; linkTarget?: string } = {
      name,
      content: (data['content'] as string) ?? '',
    };
    if (projectId !== null) createData.projectId = projectId;
    if (data['priority'] !== undefined) createData.priority = data['priority'] as number;
    if (data['linkTarget'] !== undefined) createData.linkTarget = data['linkTarget'] as string;

    return this.promptRepo.create(createData);
  }

  async deleteByName(name: string): Promise<void> {
    // Find first prompt with this name (across all projects)
    const all = await this.promptRepo.findAll();
    const match = all.find((p) => p.name === name);
    if (match === undefined) return;
    await this.promptRepo.delete(match.id);
  }

  // ── Visibility for MCP (approved prompts + session's pending requests) ──

  async getVisiblePrompts(
    projectId?: string,
    sessionId?: string,
  ): Promise<Array<{ name: string; content: string; priority: number; summary: string | null; chapters: string[] | null; linkTarget: string | null; type: 'prompt' | 'promptrequest' }>> {
    const results: Array<{ name: string; content: string; priority: number; summary: string | null; chapters: string[] | null; linkTarget: string | null; type: 'prompt' | 'promptrequest' }> = [];

    // Approved prompts (project-scoped + global)
    const prompts = await this.promptRepo.findAll(projectId);
    for (const p of prompts) {
      results.push({ name: p.name, content: p.content, priority: p.priority, summary: p.summary, chapters: p.chapters as string[] | null, linkTarget: p.linkTarget, type: 'prompt' });
    }

    // Session's own pending requests
    if (sessionId) {
      const requests = await this.promptRequestRepo.findBySession(sessionId, projectId);
      for (const r of requests) {
        results.push({ name: r.name, content: r.content, priority: 5, summary: null, chapters: null, linkTarget: null, type: 'promptrequest' });
      }
    }

    return results;
  }
}
