// Routes des services git distants (P12) : CRUD des credentials (secrets JAMAIS renvoyés), test de
// connexion, listing des dépôts distants, et lancement/suivi d'un clone. Authentifiées par le hook
// preValidation global. Les secrets sont chiffrés par SecretBox dans le manager.
import type { FastifyInstance, FastifyReply } from 'fastify';
import type {
  CloneRequest,
  CloneStartResponse,
  CloneStatusResponse,
  CreateGitCredentialRequest,
  GitCredentialResponse,
  GitCredentialsListResponse,
  RemoteReposResponse,
  UpdateGitCredentialRequest,
} from '@arboretum/shared';
import type { Db } from '../db/index.js';
import type { GitCredentialsManager } from '../core/git-credentials.js';
import type { CloneManager } from '../core/clone-manager.js';
import { getGitClient, GitServiceError } from '../core/git-clients/index.js';
import { recordAudit } from '../core/audit-log.js';

function sendError(reply: FastifyReply, err: unknown): FastifyReply {
  const e = err as { statusCode?: number; code?: string; message?: string };
  const status = typeof e.statusCode === 'number' ? e.statusCode : 500;
  return reply.status(status).send({ error: { code: e.code ?? 'INTERNAL', message: e.message ?? 'Internal error' } });
}

const GIT_SERVICE_ERROR_STATUS: Record<string, number> = { AUTH_FAILED: 401, RATE_LIMITED: 429, UNREACHABLE: 502, BAD_BASE_URL: 400 };

export function registerGitConnectionRoutes(
  app: FastifyInstance,
  credentials: GitCredentialsManager,
  clones: CloneManager,
  db: Db,
): void {
  const actor = (req: { authContext?: { tokenId: string } | null }): string => req.authContext?.tokenId ?? 'unknown';

  app.get('/api/v1/git-connections', async (): Promise<GitCredentialsListResponse> => ({ credentials: credentials.list() }));

  app.post('/api/v1/git-connections', async (req, reply) => {
    try {
      const cred = credentials.create((req.body ?? {}) as CreateGitCredentialRequest);
      recordAudit(db, { actor: actor(req), action: 'git_credential.create', resourceId: cred.id, details: { service: cred.service, authType: cred.authType } });
      const res: GitCredentialResponse = { credential: cred };
      return reply.status(201).send(res);
    } catch (err) {
      return sendError(reply, err);
    }
  });

  app.patch('/api/v1/git-connections/:id', async (req, reply) => {
    try {
      const cred = credentials.update((req.params as { id: string }).id, (req.body ?? {}) as UpdateGitCredentialRequest);
      recordAudit(db, { actor: actor(req), action: 'git_credential.update', resourceId: cred.id });
      const res: GitCredentialResponse = { credential: cred };
      return reply.send(res);
    } catch (err) {
      return sendError(reply, err);
    }
  });

  app.delete('/api/v1/git-connections/:id', async (req, reply) => {
    const { id } = req.params as { id: string };
    if (!credentials.remove(id)) {
      return reply.status(404).send({ error: { code: 'NOT_FOUND', message: 'No credential with this id' } });
    }
    recordAudit(db, { actor: actor(req), action: 'git_credential.delete', resourceId: id });
    return reply.send({ ok: true });
  });

  app.post('/api/v1/git-connections/:id/test', async (req, reply) => {
    try {
      const result = await credentials.test((req.params as { id: string }).id);
      recordAudit(db, { actor: actor(req), action: 'git_credential.test', resourceId: (req.params as { id: string }).id, details: { ok: result.ok } });
      return reply.send(result);
    } catch (err) {
      return sendError(reply, err);
    }
  });

  app.get('/api/v1/git-connections/:id/repos', async (req, reply) => {
    const { id } = req.params as { id: string };
    const q = req.query as { page?: string; search?: string };
    const ctx = credentials.authContext(id);
    if (!ctx) return reply.status(404).send({ error: { code: 'NOT_FOUND', message: 'No usable credential with this id' } });
    const page = Math.max(1, Number(q.page) || 1);
    try {
      const { repos, nextPage } = await getGitClient(ctx.service, ctx.baseUrl).listRepos(ctx.auth, page, q.search);
      const res: RemoteReposResponse = { repos, nextPage };
      return reply.send(res);
    } catch (err) {
      if (err instanceof GitServiceError) {
        return reply.status(GIT_SERVICE_ERROR_STATUS[err.errorCode] ?? 502).send({ error: { code: err.errorCode, message: err.message } });
      }
      return sendError(reply, err);
    }
  });

  // ---- Clone (asynchrone : 202 + suivi WS/REST) ----
  app.post('/api/v1/repos/clone', async (req, reply) => {
    const body = (req.body ?? {}) as Partial<CloneRequest>;
    if (typeof body.credentialId !== 'string' || typeof body.remoteUrl !== 'string' || typeof body.dest !== 'string') {
      return reply.status(400).send({ error: { code: 'BAD_REQUEST', message: 'credentialId, remoteUrl and dest are required' } });
    }
    try {
      const operationId = await clones.start({ credentialId: body.credentialId, remoteUrl: body.remoteUrl, dest: body.dest }, actor(req));
      const res: CloneStartResponse = { operationId };
      return reply.status(202).send(res);
    } catch (err) {
      return sendError(reply, err);
    }
  });

  app.get('/api/v1/repos/clone/:id', async (req, reply) => {
    const op = clones.get((req.params as { id: string }).id);
    if (!op) return reply.status(404).send({ error: { code: 'NOT_FOUND', message: 'No clone operation with this id' } });
    const res: CloneStatusResponse = { operation: op };
    return reply.send(res);
  });
}
