import type { FastifyInstance } from 'fastify';

import type { ResourceProposalService } from '../services/resource-proposal.service.js';
import type { IProjectRepository } from '../repositories/project.repository.js';
import type { IAgentRepository } from '../repositories/agent.repository.js';
import type {
  ResourceType,
} from '../repositories/resource-revision.repository.js';
import type { ProposalStatus } from '../repositories/resource-proposal.repository.js';

interface ProposalRouteDeps {
  proposalService: ResourceProposalService;
  projectRepo: IProjectRepository;
  agentRepo?: IAgentRepository;
}

const VALID_TYPES: readonly ResourceType[] = ['prompt', 'skill'] as const;
const VALID_STATUSES: readonly ProposalStatus[] = ['pending', 'approved', 'rejected'] as const;

export function registerProposalRoutes(app: FastifyInstance, deps: ProposalRouteDeps): void {
  const { proposalService, projectRepo, agentRepo } = deps;

  app.get<{ Querystring: { resourceType?: string; status?: string; project?: string; agent?: string } }>(
    '/api/v1/proposals',
    async (request) => {
      const filter: {
        resourceType?: ResourceType;
        status?: ProposalStatus;
        projectId?: string;
        agentId?: string;
      } = {};
      const { resourceType, status, project, agent } = request.query;
      if (resourceType !== undefined) {
        if (!VALID_TYPES.includes(resourceType as ResourceType)) {
          throw Object.assign(new Error(`Invalid resourceType: ${resourceType}`), { statusCode: 400 });
        }
        filter.resourceType = resourceType as ResourceType;
      }
      if (status !== undefined) {
        if (!VALID_STATUSES.includes(status as ProposalStatus)) {
          throw Object.assign(new Error(`Invalid status: ${status}`), { statusCode: 400 });
        }
        filter.status = status as ProposalStatus;
      }
      if (project !== undefined) {
        const proj = await projectRepo.findByName(project);
        if (proj === null) {
          throw Object.assign(new Error(`Project not found: ${project}`), { statusCode: 404 });
        }
        filter.projectId = proj.id;
      }
      if (agent !== undefined) {
        if (!agentRepo) {
          throw Object.assign(new Error('Agent scoping not enabled'), { statusCode: 500 });
        }
        const ag = await agentRepo.findByName(agent);
        if (ag === null) {
          throw Object.assign(new Error(`Agent not found: ${agent}`), { statusCode: 404 });
        }
        filter.agentId = ag.id;
      }
      return proposalService.list(filter);
    },
  );

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

  app.post('/api/v1/proposals', async (request, reply) => {
    const body = request.body as Record<string, unknown>;
    const resourceType = body['resourceType'];
    if (typeof resourceType !== 'string' || !VALID_TYPES.includes(resourceType as ResourceType)) {
      throw Object.assign(new Error('resourceType must be "prompt" or "skill"'), { statusCode: 400 });
    }
    const name = body['name'];
    if (typeof name !== 'string' || name.length === 0) {
      throw Object.assign(new Error('name is required'), { statusCode: 400 });
    }
    const proposalBody = body['body'];
    if (proposalBody === undefined || typeof proposalBody !== 'object' || proposalBody === null) {
      throw Object.assign(new Error('body must be an object'), { statusCode: 400 });
    }
    const input: {
      resourceType: ResourceType;
      name: string;
      body: Record<string, unknown>;
      projectId?: string;
      agentId?: string;
      createdBySession?: string;
      createdByUserId?: string;
    } = {
      resourceType: resourceType as ResourceType,
      name,
      body: proposalBody as Record<string, unknown>,
    };
    if (typeof body['project'] === 'string') {
      const proj = await projectRepo.findByName(body['project']);
      if (proj === null) {
        throw Object.assign(new Error(`Project not found: ${body['project']}`), { statusCode: 404 });
      }
      input.projectId = proj.id;
    } else if (typeof body['projectId'] === 'string') {
      input.projectId = body['projectId'];
    }
    if (typeof body['agent'] === 'string') {
      if (!agentRepo) {
        throw Object.assign(new Error('Agent scoping not enabled'), { statusCode: 500 });
      }
      const ag = await agentRepo.findByName(body['agent']);
      if (ag === null) {
        throw Object.assign(new Error(`Agent not found: ${body['agent']}`), { statusCode: 404 });
      }
      input.agentId = ag.id;
    } else if (typeof body['agentId'] === 'string') {
      input.agentId = body['agentId'];
    }
    if (typeof body['createdBySession'] === 'string') input.createdBySession = body['createdBySession'];
    if (typeof body['createdByUserId'] === 'string') input.createdByUserId = body['createdByUserId'];

    const proposal = await proposalService.propose(input);
    reply.code(201);
    return proposal;
  });

  app.put<{ Params: { id: string }; Body: { body?: Record<string, unknown> } }>(
    '/api/v1/proposals/:id',
    async (request) => {
      const proposalBody = request.body.body;
      if (proposalBody === undefined) {
        throw Object.assign(new Error('body is required'), { statusCode: 400 });
      }
      return proposalService.updateBody(request.params.id, proposalBody);
    },
  );

  app.post<{ Params: { id: string } }>('/api/v1/proposals/:id/approve', async (request) => {
    // approverUserId is set by the auth middleware on the request — we
    // don't grab it explicitly here; service uses what the audit layer
    // already records. Threading it through requires the auth context
    // (out of scope for PR-2; PR-4's reviewer flow will surface it).
    return proposalService.approve(request.params.id);
  });

  app.post<{ Params: { id: string }; Body: { reason?: string; reviewerNote?: string } }>(
    '/api/v1/proposals/:id/reject',
    async (request) => {
      const note = request.body.reviewerNote ?? request.body.reason ?? '';
      return proposalService.reject(request.params.id, note);
    },
  );

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