/**
 * API Versioning routes — version lifecycle, client pins, deprecation.
 * @module api-versioning/routes
 */

import type { FastifyInstance } from 'fastify';
import {
  UnauthorizedError,
  ForbiddenError,
  NotFoundError,
  ConflictError,
} from '../../lib/errors.js';
import { getRequestProductId } from '../../lib/request-context.js';
import {
  CreateApiVersionSchema,
  UpdateApiVersionSchema,
  PinClientVersionSchema,
  UpdateClientPinSchema,
  type VersionStatus,
} from './types.js';
import * as repo from './repository.js';

function requireAuth(req: { jwtPayload?: { sub: string } }): string {
  if (!req.jwtPayload?.sub) throw new UnauthorizedError('Authentication required');
  return req.jwtPayload.sub;
}

function requireAdmin(req: { jwtPayload?: { sub: string; role?: string } }): string {
  const userId = requireAuth(req);
  if (req.jwtPayload?.role !== 'admin') throw new ForbiddenError('Admin access required');
  return userId;
}

export async function apiVersioningRoutes(app: FastifyInstance): Promise<void> {
  // ── Create version ─────────────────────────────────────────
  app.post('/api-versions', async (req, reply) => {
    const userId = requireAdmin(req);
    const productId = getRequestProductId(req);
    const input = CreateApiVersionSchema.parse(req.body);

    // Check uniqueness
    const existing = await repo.getVersionByString(productId, input.version);
    if (existing) {
      throw new ConflictError(`API version "${input.version}" already exists`);
    }

    const now = new Date().toISOString();
    const doc = {
      id: `ver_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
      productId,
      version: input.version,
      status: input.status,
      label: input.label,
      releasedAt: input.releasedAt ?? null,
      deprecatedAt: null,
      sunsetAt: null,
      migrationGuide: input.migrationGuide ?? null,
      breakingChanges: input.breakingChanges,
      addedEndpoints: input.addedEndpoints,
      removedEndpoints: input.removedEndpoints,
      modifiedEndpoints: input.modifiedEndpoints,
      createdAt: now,
      updatedAt: now,
      updatedBy: userId,
    };

    const created = await repo.createVersion(doc);
    req.log.info({ versionId: created.id, version: input.version }, 'API version created');
    reply.status(201);
    return created;
  });

  // ── List versions ──────────────────────────────────────────
  app.get('/api-versions', async req => {
    requireAuth(req);
    const productId = getRequestProductId(req);
    const { status, limit: limitStr } = req.query as { status?: string; limit?: string };
    const parsedLimit = limitStr ? parseInt(limitStr, 10) : 50;
    const safeLimit =
      Number.isFinite(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, 200) : 50;

    return repo.listVersions(productId, {
      status: status as VersionStatus | undefined,
      limit: safeLimit,
    });
  });

  // ── Get current active version ─────────────────────────────
  app.get('/api-versions/current', async req => {
    requireAuth(req);
    const productId = getRequestProductId(req);
    const active = await repo.getActiveVersion(productId);
    if (!active) throw new NotFoundError('No active API version found');
    return active;
  });

  // ── Get version ────────────────────────────────────────────
  app.get<{ Params: { id: string } }>('/api-versions/:id', async req => {
    requireAuth(req);
    const productId = getRequestProductId(req);
    const version = await repo.getVersion(req.params.id, productId);
    if (!version) throw new NotFoundError('API version not found');
    return version;
  });

  // ── Update version ─────────────────────────────────────────
  app.patch<{ Params: { id: string } }>('/api-versions/:id', async req => {
    const userId = requireAdmin(req);
    const productId = getRequestProductId(req);
    const input = UpdateApiVersionSchema.parse(req.body);

    const updated = await repo.updateVersion(req.params.id, productId, {
      ...input,
      updatedBy: userId,
    });
    if (!updated) throw new NotFoundError('API version not found');

    req.log.info({ versionId: req.params.id, status: input.status }, 'API version updated');
    return updated;
  });

  // ── Delete version (draft only) ────────────────────────────
  app.delete<{ Params: { id: string } }>('/api-versions/:id', async (req, reply) => {
    requireAdmin(req);
    const productId = getRequestProductId(req);

    const version = await repo.getVersion(req.params.id, productId);
    if (!version) throw new NotFoundError('API version not found');
    if (version.status !== 'draft') {
      throw new ConflictError('Only draft versions can be deleted');
    }

    await repo.deleteVersion(req.params.id, productId);
    req.log.info({ versionId: req.params.id }, 'API version deleted');
    reply.status(204);
    return;
  });

  // ── Pin client to version ──────────────────────────────────
  app.post('/api-versions/pins', async (req, reply) => {
    const userId = requireAdmin(req);
    const productId = getRequestProductId(req);
    const input = PinClientVersionSchema.parse(req.body);

    // Verify version exists
    const version = await repo.getVersionByString(productId, input.pinnedVersion);
    if (!version) throw new NotFoundError(`API version "${input.pinnedVersion}" not found`);

    // Check for existing pin
    const existing = await repo.getPin(input.clientId, productId);
    if (existing) {
      throw new ConflictError(`Client "${input.clientId}" already has a version pin`);
    }

    const now = new Date().toISOString();
    const doc = {
      id: `pin_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
      productId,
      clientId: input.clientId,
      pinnedVersion: input.pinnedVersion,
      reason: input.reason,
      autoUpgradeAt: input.autoUpgradeAt ?? null,
      createdAt: now,
      updatedAt: now,
      updatedBy: userId,
    };

    const created = await repo.createPin(doc);
    req.log.info(
      { clientId: input.clientId, version: input.pinnedVersion },
      'Client version pinned'
    );
    reply.status(201);
    return created;
  });

  // ── List pins ──────────────────────────────────────────────
  app.get('/api-versions/pins', async req => {
    requireAdmin(req);
    const productId = getRequestProductId(req);
    const { version, limit: limitStr } = req.query as { version?: string; limit?: string };
    const parsedLimit = limitStr ? parseInt(limitStr, 10) : 50;
    const safeLimit =
      Number.isFinite(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, 200) : 50;

    return repo.listPins(productId, { version, limit: safeLimit });
  });

  // ── Update pin ─────────────────────────────────────────────
  app.patch<{ Params: { id: string } }>('/api-versions/pins/:id', async req => {
    const userId = requireAdmin(req);
    const productId = getRequestProductId(req);
    const input = UpdateClientPinSchema.parse(req.body);

    const updated = await repo.updatePin(req.params.id, productId, {
      ...input,
      updatedBy: userId,
    });
    if (!updated) throw new NotFoundError('Version pin not found');

    req.log.info({ pinId: req.params.id }, 'Version pin updated');
    return updated;
  });

  // ── Delete pin ─────────────────────────────────────────────
  app.delete<{ Params: { id: string } }>('/api-versions/pins/:id', async (req, reply) => {
    requireAdmin(req);
    const productId = getRequestProductId(req);

    const deleted = await repo.deletePin(req.params.id, productId);
    if (!deleted) throw new NotFoundError('Version pin not found');

    req.log.info({ pinId: req.params.id }, 'Version pin deleted');
    reply.status(204);
    return;
  });
}
