import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
import { isMcpToken } from '@mcpctl/shared';
import type { McpTokenService } from '../services/mcp-token.service.js';
import { PermissionCeilingError } from '../services/mcp-token.service.js';
import { NotFoundError, ConflictError } from '../services/mcp-server.service.js';
import type { IProjectRepository } from '../repositories/project.repository.js';

export interface McpTokenRouteDeps {
  tokenService: McpTokenService;
  projectRepo: IProjectRepository;
}

export function registerMcpTokenRoutes(app: FastifyInstance, deps: McpTokenRouteDeps): void {
  const { tokenService, projectRepo } = deps;

  // ── List ─────────────────────────────────────────────────────────────
  app.get<{ Querystring: { projectId?: string; projectName?: string; includeRevoked?: string } }>(
    '/api/v1/mcptokens',
    async (request) => {
      const { projectId, projectName, includeRevoked } = request.query;

      // Allow filtering by project name for CLI ergonomics.
      let resolvedProjectId = projectId;
      if (resolvedProjectId === undefined && projectName !== undefined) {
        const project = await projectRepo.findByName(projectName);
        if (project === null) throw new NotFoundError(`Project not found: ${projectName}`);
        resolvedProjectId = project.id;
      }

      const filter: { projectId?: string; includeRevoked?: boolean } = {};
      if (resolvedProjectId !== undefined) filter.projectId = resolvedProjectId;
      if (includeRevoked === 'true') filter.includeRevoked = true;

      const rows = await tokenService.list(filter);
      return rows.map(toListResponse);
    },
  );

  // ── Describe ─────────────────────────────────────────────────────────
  app.get<{ Params: { id: string } }>('/api/v1/mcptokens/:id', async (request) => {
    const row = await tokenService.getById(request.params.id);
    return toListResponse(row);
  });

  // ── Create ───────────────────────────────────────────────────────────
  app.post('/api/v1/mcptokens', async (request, reply) => {
    const userId = request.userId;
    if (userId === undefined) {
      reply.code(401);
      return { error: 'Not authenticated' };
    }

    try {
      // Accept projectName OR projectId for CLI ergonomics.
      const body = (request.body ?? {}) as Record<string, unknown>;
      if (typeof body['projectName'] === 'string' && typeof body['projectId'] !== 'string') {
        const project = await projectRepo.findByName(body['projectName']);
        if (project === null) throw new NotFoundError(`Project not found: ${body['projectName']}`);
        body['projectId'] = project.id;
      }

      const result = await tokenService.create(userId, body);
      reply.code(201);
      return {
        ...toListResponse(result.mcpToken),
        token: result.raw,
      };
    } catch (err) {
      if (err instanceof NotFoundError) {
        reply.code(404);
        return { error: err.message };
      }
      if (err instanceof ConflictError) {
        reply.code(409);
        return { error: err.message };
      }
      if (err instanceof PermissionCeilingError) {
        reply.code(403);
        return { error: err.message };
      }
      throw err;
    }
  });

  // ── Revoke (soft-delete) ────────────────────────────────────────────
  app.post<{ Params: { id: string } }>('/api/v1/mcptokens/:id/revoke', async (request) => {
    const row = await tokenService.revoke(request.params.id);
    return toListResponse(row);
  });

  // ── Delete (hard) ────────────────────────────────────────────────────
  app.delete<{ Params: { id: string } }>('/api/v1/mcptokens/:id', async (request, reply) => {
    await tokenService.delete(request.params.id);
    reply.code(204);
  });

  // ── Introspect ───────────────────────────────────────────────────────
  // Called by mcplocal's HTTP-mode auth preHandler to resolve a raw bearer
  // to principal info. Accepts a McpToken bearer directly — bypasses the
  // session-auth path.
  app.get('/api/v1/mcptokens/introspect', async (request: FastifyRequest, reply: FastifyReply) => {
    const header = request.headers.authorization;
    if (header === undefined || !header.startsWith('Bearer ')) {
      reply.code(401);
      return { ok: false, error: 'Missing Authorization' };
    }
    const token = header.slice(7);
    if (!isMcpToken(token)) {
      reply.code(401);
      return { ok: false, error: 'Not a mcptoken bearer' };
    }
    const result = await tokenService.introspectRaw(token);
    if (!result.ok) {
      reply.code(401);
    }
    return result;
  });
}

function toListResponse(row: import('../repositories/interfaces.js').McpTokenWithRelations): Record<string, unknown> {
  return {
    id: row.id,
    name: row.name,
    projectId: row.projectId,
    projectName: row.project.name,
    tokenPrefix: row.tokenPrefix,
    ownerId: row.ownerId,
    ownerEmail: row.owner.email,
    description: row.description,
    createdAt: row.createdAt,
    expiresAt: row.expiresAt,
    lastUsedAt: row.lastUsedAt,
    revokedAt: row.revokedAt,
    status: statusOf(row),
  };
}

function statusOf(row: import('../repositories/interfaces.js').McpTokenWithRelations): 'active' | 'revoked' | 'expired' {
  if (row.revokedAt !== null) return 'revoked';
  if (row.expiresAt !== null && row.expiresAt < new Date()) return 'expired';
  return 'active';
}
