import type { FastifyInstance } from 'fastify';
import type { Prompt } from '@prisma/client';
import type { PromptService } from '../services/prompt.service.js';
import type { IProjectRepository, ProjectWithRelations } from '../repositories/project.repository.js';
import type { IAgentRepository } from '../repositories/agent.repository.js';

type PromptWithLinkStatus = Prompt & { linkStatus: 'alive' | 'dead' | null };

/**
 * Enrich prompts with linkStatus by checking if the target project/server exists.
 * This is a structural check (does the target exist?) — not a runtime probe.
 */
async function enrichWithLinkStatus(
  prompts: Prompt[],
  projectRepo: IProjectRepository,
): Promise<PromptWithLinkStatus[]> {
  // Cache project lookups to avoid repeated DB queries
  const projectCache = new Map<string, ProjectWithRelations | null>();

  const results: PromptWithLinkStatus[] = [];

  for (const p of prompts) {
    if (!p.linkTarget) {
      results.push({ ...p, linkStatus: null } as PromptWithLinkStatus);
      continue;
    }

    try {
      // Parse: project/server:uri
      const slashIdx = p.linkTarget.indexOf('/');
      if (slashIdx < 1) { results.push({ ...p, linkStatus: 'dead' as const }); continue; }
      const projectName = p.linkTarget.slice(0, slashIdx);
      const rest = p.linkTarget.slice(slashIdx + 1);
      const colonIdx = rest.indexOf(':');
      if (colonIdx < 1) { results.push({ ...p, linkStatus: 'dead' as const }); continue; }
      const serverName = rest.slice(0, colonIdx);

      // Check if project exists (cached)
      if (!projectCache.has(projectName)) {
        projectCache.set(projectName, await projectRepo.findByName(projectName));
      }
      const project = projectCache.get(projectName);
      if (!project) { results.push({ ...p, linkStatus: 'dead' as const }); continue; }

      // Check if server is linked to that project
      const hasServer = project.servers.some((s) => s.server.name === serverName);
      results.push({ ...p, linkStatus: hasServer ? 'alive' as const : 'dead' as const });
    } catch {
      results.push({ ...p, linkStatus: 'dead' as const });
    }
  }

  return results;
}

export function registerPromptRoutes(
  app: FastifyInstance,
  service: PromptService,
  projectRepo: IProjectRepository,
  agentRepo?: IAgentRepository,
): void {
  // ── Prompts (approved) ──

  app.get<{ Querystring: { project?: string; scope?: string; projectId?: string } }>('/api/v1/prompts', async (request) => {
    let prompts: Prompt[];
    const projectName = request.query.project;
    if (projectName) {
      const project = await projectRepo.findByName(projectName);
      if (!project) {
        throw Object.assign(new Error(`Project not found: ${projectName}`), { statusCode: 404 });
      }
      prompts = await service.listPrompts(project.id);
    } else if (request.query.projectId) {
      prompts = await service.listPrompts(request.query.projectId);
    } else if (request.query.scope === 'global') {
      prompts = await service.listGlobalPrompts();
    } else {
      prompts = await service.listPrompts();
    }
    return enrichWithLinkStatus(prompts, projectRepo);
  });

  app.get<{ Params: { id: string } }>('/api/v1/prompts/:id', async (request) => {
    const prompt = await service.getPrompt(request.params.id);
    const [enriched] = await enrichWithLinkStatus([prompt], projectRepo);
    return enriched;
  });

  app.post('/api/v1/prompts', async (request, reply) => {
    // Resolve `agent: <name>` and `project: <name>` to FK ids before
    // handing off to the service. Mirrors the existing project-name
    // resolution on `/api/v1/promptrequests`.
    const body = request.body as Record<string, unknown>;
    const resolved: Record<string, unknown> = { ...body };
    if (typeof body['project'] === 'string') {
      const project = await projectRepo.findByName(body['project']);
      if (!project) {
        throw Object.assign(new Error(`Project not found: ${body['project']}`), { statusCode: 404 });
      }
      resolved['projectId'] = project.id;
      delete resolved['project'];
    }
    if (typeof body['agent'] === 'string') {
      if (!agentRepo) {
        throw Object.assign(new Error('Agent-scoped prompts not enabled'), { statusCode: 500 });
      }
      const agent = await agentRepo.findByName(body['agent']);
      if (!agent) {
        throw Object.assign(new Error(`Agent not found: ${body['agent']}`), { statusCode: 404 });
      }
      resolved['agentId'] = agent.id;
      delete resolved['agent'];
    }
    const prompt = await service.createPrompt(resolved);
    reply.code(201);
    return prompt;
  });

  app.put<{ Params: { id: string } }>('/api/v1/prompts/:id', async (request) => {
    return service.updatePrompt(request.params.id, request.body);
  });

  app.delete<{ Params: { id: string } }>('/api/v1/prompts/:id', async (request, reply): Promise<unknown> => {
    const result = await service.deletePrompt(request.params.id);
    if (result) {
      // System prompt was reset to default — return the reset prompt
      return result;
    }
    reply.code(204);
    return undefined;
  });

  // ── Prompt Requests (pending proposals) ──

  app.get<{ Querystring: { project?: string; scope?: string } }>('/api/v1/promptrequests', async (request) => {
    const projectName = request.query.project;
    if (projectName) {
      const project = await projectRepo.findByName(projectName);
      if (!project) {
        throw Object.assign(new Error(`Project not found: ${projectName}`), { statusCode: 404 });
      }
      return service.listPromptRequests(project.id);
    }
    if (request.query.scope === 'global') {
      return service.listGlobalPromptRequests();
    }
    return service.listPromptRequests();
  });

  app.get<{ Params: { id: string } }>('/api/v1/promptrequests/:id', async (request) => {
    return service.getPromptRequest(request.params.id);
  });

  app.put<{ Params: { id: string } }>('/api/v1/promptrequests/:id', async (request) => {
    return service.updatePromptRequest(request.params.id, request.body);
  });

  app.delete<{ Params: { id: string } }>('/api/v1/promptrequests/:id', async (request, reply) => {
    await service.deletePromptRequest(request.params.id);
    reply.code(204);
  });

  app.post('/api/v1/promptrequests', async (request, reply) => {
    const body = request.body as Record<string, unknown>;
    // Resolve project name → ID if provided
    if (body.project && typeof body.project === 'string') {
      const project = await projectRepo.findByName(body.project);
      if (!project) {
        throw Object.assign(new Error(`Project not found: ${body.project}`), { statusCode: 404 });
      }
      const { project: _, ...rest } = body;
      const req = await service.propose({ ...rest, projectId: project.id });
      reply.code(201);
      return req;
    }
    const req = await service.propose(body);
    reply.code(201);
    return req;
  });

  // Approve: atomic delete request → create prompt
  app.post<{ Params: { id: string } }>('/api/v1/promptrequests/:id/approve', async (request) => {
    return service.approve(request.params.id);
  });

  // Regenerate summary/chapters for a prompt
  app.post<{ Params: { id: string } }>('/api/v1/prompts/:id/regenerate-summary', async (request) => {
    return service.regenerateSummary(request.params.id);
  });

  // Compact prompt index for gating LLM (name, priority, summary, chapters)
  app.get<{ Params: { name: string } }>('/api/v1/projects/:name/prompt-index', async (request) => {
    const project = await projectRepo.findByName(request.params.name);
    if (!project) {
      throw Object.assign(new Error(`Project not found: ${request.params.name}`), { statusCode: 404 });
    }
    const prompts = await service.listPrompts(project.id);
    return prompts.map((p) => ({
      name: p.name,
      priority: p.priority,
      summary: p.summary,
      chapters: p.chapters,
      linkTarget: p.linkTarget,
    }));
  });

  // ── Project-scoped endpoints (for mcplocal) ──

  // Visible prompts: approved + session's pending requests
  app.get<{ Params: { name: string }; Querystring: { session?: string } }>(
    '/api/v1/projects/:name/prompts/visible',
    async (request) => {
      const project = await projectRepo.findByName(request.params.name);
      if (!project) {
        throw Object.assign(new Error(`Project not found: ${request.params.name}`), { statusCode: 404 });
      }
      return service.getVisiblePrompts(project.id, request.query.session);
    },
  );

  // LLM propose: create a PromptRequest for a project
  app.post<{ Params: { name: string } }>(
    '/api/v1/projects/:name/promptrequests',
    async (request, reply) => {
      const project = await projectRepo.findByName(request.params.name);
      if (!project) {
        throw Object.assign(new Error(`Project not found: ${request.params.name}`), { statusCode: 404 });
      }
      const body = request.body as Record<string, unknown>;
      const req = await service.propose({
        ...body,
        projectId: project.id,
      });
      reply.code(201);
      return req;
    },
  );

  // ── Agent-direct prompts ──
  //
  // Lists prompts whose `Prompt.agentId` matches the agent. These prompts
  // are *not* in any project — they're "always-on" overlays for the agent,
  // injected after agent.systemPrompt and before project prompts in the
  // chat system block.
  app.get<{ Params: { agentName: string } }>(
    '/api/v1/agents/:agentName/prompts',
    async (request, reply) => {
      if (!agentRepo) {
        throw Object.assign(new Error('Agent-scoped prompts not enabled'), { statusCode: 500 });
      }
      const agent = await agentRepo.findByName(request.params.agentName);
      if (!agent) {
        reply.code(404);
        return { error: `Agent not found: ${request.params.agentName}` };
      }
      const prompts = await service.listPromptsForAgent(agent.id);
      return enrichWithLinkStatus(prompts, projectRepo);
    },
  );
}
