/**
 * Data Retention routes — policy CRUD, run enforcement, job history.
 * @module retention/routes
 */

import type { FastifyInstance } from 'fastify';
import { UnauthorizedError, ForbiddenError, NotFoundError } from '../../lib/errors.js';
import { getRequestProductId } from '../../lib/request-context.js';
import {
  CreateRetentionPolicySchema,
  UpdateRetentionPolicySchema,
  getCutoffDate,
  type RetentionPolicyDoc,
  type RetentionJobDoc,
  type RetentionJobStatus,
} from './types.js';
import * as repo from './repository.js';

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

export async function retentionRoutes(app: FastifyInstance): Promise<void> {
  // ── Create policy ──────────────────────────────────────────
  app.post('/retention/policies', async (req, reply) => {
    const userId = requireAdmin(req);
    const productId = getRequestProductId(req);
    const input = CreateRetentionPolicySchema.parse(req.body);

    const now = new Date().toISOString();
    const doc: RetentionPolicyDoc = {
      id: `ret_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
      productId,
      containerName: input.containerName,
      label: input.label,
      retentionDays: input.retentionDays,
      action: input.action,
      dateField: input.dateField,
      filter: input.filter,
      isEnabled: input.isEnabled,
      schedule: input.schedule,
      lastRunAt: null,
      lastRunPurgedCount: 0,
      createdAt: now,
      updatedAt: now,
      updatedBy: userId,
    };

    const created = await repo.createPolicy(doc);
    req.log.info(
      { policyId: created.id, container: input.containerName },
      'Retention policy created'
    );
    reply.status(201);
    return created;
  });

  // ── List policies ──────────────────────────────────────────
  app.get('/retention/policies', async req => {
    requireAdmin(req);
    const productId = getRequestProductId(req);
    const { enabled, limit: limitStr } = req.query as { enabled?: string; limit?: string };

    const parsedLimit = limitStr ? parseInt(limitStr, 10) : 50;
    const safeLimit =
      Number.isFinite(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, 200) : 50;

    return repo.listPolicies(productId, {
      isEnabled: enabled === 'true' ? true : enabled === 'false' ? false : undefined,
      limit: safeLimit,
    });
  });

  // ── Get policy ─────────────────────────────────────────────
  app.get<{ Params: { id: string } }>('/retention/policies/:id', async req => {
    requireAdmin(req);
    const productId = getRequestProductId(req);
    const policy = await repo.getPolicy(req.params.id, productId);
    if (!policy) throw new NotFoundError('Retention policy not found');
    return policy;
  });

  // ── Update policy ──────────────────────────────────────────
  app.patch<{ Params: { id: string } }>('/retention/policies/:id', async req => {
    const userId = requireAdmin(req);
    const productId = getRequestProductId(req);
    const input = UpdateRetentionPolicySchema.parse(req.body);

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

    req.log.info({ policyId: req.params.id }, 'Retention policy updated');
    return updated;
  });

  // ── Delete policy ──────────────────────────────────────────
  app.delete<{ Params: { id: string } }>('/retention/policies/:id', async (req, reply) => {
    requireAdmin(req);
    const productId = getRequestProductId(req);
    const deleted = await repo.deletePolicy(req.params.id, productId);
    if (!deleted) throw new NotFoundError('Retention policy not found');
    req.log.info({ policyId: req.params.id }, 'Retention policy deleted');
    reply.status(204);
    return;
  });

  // ── Run enforcement (dry-run or execute) ───────────────────
  app.post('/retention/enforce', async req => {
    const userId = requireAdmin(req);
    const productId = getRequestProductId(req);
    const { dryRun, policyId } = req.body as { dryRun?: boolean; policyId?: string };

    let policies: RetentionPolicyDoc[];
    if (policyId) {
      const policy = await repo.getPolicy(policyId, productId);
      if (!policy) throw new NotFoundError('Retention policy not found');
      policies = [policy];
    } else {
      policies = await repo.getEnabledPolicies(productId);
    }

    const results: Array<{
      policyId: string;
      containerName: string;
      cutoffDate: string;
      wouldPurge: number;
      purged: number;
    }> = [];

    for (const policy of policies) {
      const cutoffDate = getCutoffDate(policy.retentionDays);

      // In production, would query target container and delete/archive/anonymize.
      // For MVP, simulate with count estimation.
      const estimatedCount = 0; // Would be a real query in production

      if (!dryRun) {
        const now = new Date().toISOString();
        const job: RetentionJobDoc = {
          id: `rjob_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
          productId,
          policyId: policy.id,
          containerName: policy.containerName,
          status: 'completed',
          scannedCount: estimatedCount,
          purgedCount: estimatedCount,
          errorCount: 0,
          error: null,
          startedAt: now,
          completedAt: now,
        };
        await repo.createJob(job);

        // Update policy last run stats
        await repo.updatePolicy(policy.id, productId, {
          lastRunAt: now,
          lastRunPurgedCount: estimatedCount,
          updatedBy: userId,
        });
      }

      results.push({
        policyId: policy.id,
        containerName: policy.containerName,
        cutoffDate,
        wouldPurge: estimatedCount,
        purged: dryRun ? 0 : estimatedCount,
      });
    }

    req.log.info(
      { productId, userId, dryRun, policyCount: policies.length },
      'Retention enforcement executed'
    );

    return { dryRun: dryRun ?? false, policiesEvaluated: policies.length, results };
  });

  // ── List jobs (audit trail) ────────────────────────────────
  app.get('/retention/jobs', async req => {
    requireAdmin(req);
    const productId = getRequestProductId(req);
    const {
      policyId,
      status,
      limit: limitStr,
    } = req.query as {
      policyId?: string;
      status?: string;
      limit?: string;
    };
    const parsedLimit = limitStr ? parseInt(limitStr, 10) : 20;
    const safeLimit =
      Number.isFinite(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, 100) : 20;

    return repo.listJobs(productId, {
      policyId,
      status: status as RetentionJobStatus | undefined,
      limit: safeLimit,
    });
  });

  // ── Stats ──────────────────────────────────────────────────
  app.get('/retention/stats', async req => {
    requireAdmin(req);
    const productId = getRequestProductId(req);
    return repo.getRetentionStats(productId);
  });
}
