import type { FastifyInstance } from 'fastify';
import { requireJwtOrApiKey } from '../../lib/api-key-auth.js';
import { BadRequestError } from '../../lib/errors.js';
import {
  CreateRunSchema,
  CreateRunStepSchema,
  ListRunsQuerySchema,
  UpdateRunSchema,
  UpdateRunStepSchema,
} from './types.js';
import * as repo from './repository.js';
import * as tracker from './tracker.js';

export async function runRoutes(app: FastifyInstance) {
  async function requireRunsRead(req: import('fastify').FastifyRequest): Promise<string> {
    const access = await requireJwtOrApiKey(req, {
      jwtRoles: ['super_admin', 'admin'],
      apiKeyScopes: ['jobs:read'],
      apiKeyTokenTypes: ['service_api'],
      rateLimitKey: 'jobs:read',
    });
    return access.productId;
  }

  async function requireRunsWrite(req: import('fastify').FastifyRequest): Promise<{
    productId: string;
    actorId: string;
  }> {
    const access = await requireJwtOrApiKey(req, {
      jwtRoles: ['super_admin', 'admin'],
      apiKeyTokenTypes: ['service_api'],
      rateLimitKey: 'jobs:write',
    });
    return { productId: access.productId, actorId: access.actorId };
  }

  app.get('/runs', async req => {
    const productId = await requireRunsRead(req);
    const parsed = ListRunsQuerySchema.safeParse(req.query);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(issue => issue.message).join('; '));
    }

    return repo.listRuns(productId, parsed.data);
  });

  app.get('/runs/:id', async req => {
    const productId = await requireRunsRead(req);
    const { id } = req.params as { id: string };
    return repo.getRun(id, productId);
  });

  app.get('/runs/:id/steps', async req => {
    const productId = await requireRunsRead(req);
    const { id } = req.params as { id: string };
    return repo.listRunSteps(productId, id);
  });

  app.post('/runs', async req => {
    const { productId, actorId } = await requireRunsWrite(req);
    const parsed = CreateRunSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(issue => issue.message).join('; '));
    }

    return tracker.startRun({
      ...parsed.data,
      productId,
      triggeredBy: parsed.data.triggeredBy ?? actorId,
    });
  });

  app.patch('/runs/:id', async req => {
    const { productId } = await requireRunsWrite(req);
    const { id } = req.params as { id: string };
    const parsed = UpdateRunSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(issue => issue.message).join('; '));
    }

    if (parsed.data.status === 'succeeded') {
      return tracker.completeRun(id, productId, parsed.data.output);
    }

    if (parsed.data.status === 'failed') {
      return tracker.failRun(id, productId, parsed.data.error ?? 'Run failed', parsed.data.output);
    }

    return repo.updateRun(id, productId, {
      status: parsed.data.status,
      output: parsed.data.output,
      error: parsed.data.error,
      completedAt: parsed.data.status === 'cancelled' ? new Date().toISOString() : undefined,
    });
  });

  app.post('/runs/:id/steps', async req => {
    const { productId } = await requireRunsWrite(req);
    const { id } = req.params as { id: string };
    const parsed = CreateRunStepSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(issue => issue.message).join('; '));
    }

    return tracker.startRunStep({
      runId: id,
      productId,
      ...parsed.data,
    });
  });

  app.patch('/runs/:id/steps/:stepName', async req => {
    const { productId } = await requireRunsWrite(req);
    const { id, stepName } = req.params as { id: string; stepName: string };
    const parsed = UpdateRunStepSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(issue => issue.message).join('; '));
    }

    if (parsed.data.status === 'succeeded') {
      return tracker.completeRunStep(id, productId, stepName, parsed.data.output);
    }

    if (parsed.data.status === 'failed') {
      return tracker.failRunStep(
        id,
        productId,
        stepName,
        parsed.data.error ?? 'Run step failed',
        parsed.data.output
      );
    }

    return repo.updateRunStep(`${id}:${stepName}`, id, productId, {
      status: parsed.data.status,
      output: parsed.data.output,
      error: parsed.data.error,
      completedAt:
        parsed.data.status === 'cancelled' || parsed.data.status === 'skipped'
          ? new Date().toISOString()
          : undefined,
    });
  });
}
