import { randomUUID } from 'node:crypto';
import type { FastifyInstance } from 'fastify';
import { BadRequestError, ForbiddenError } from '../../lib/errors.js';
import {
  CreateReviewSchema,
  ListReviewsQuerySchema,
  ReviewDecisionSchema,
  ReviewItemDoc,
  UpdateReviewSchema,
} from './types.js';
import * as repo from './repository.js';
import { notifyReviewAssigned } from './notifications.js';

function requireAdmin(req: { jwtPayload?: { sub?: string; role?: string; productId?: string } }): {
  userId: string;
  productId: string;
} {
  const payload = req.jwtPayload;
  if (!payload?.sub) {
    throw new ForbiddenError('Authentication required');
  }
  if (!payload.role || !['super_admin', 'admin'].includes(payload.role)) {
    throw new ForbiddenError('Admin access required');
  }
  return {
    userId: payload.sub,
    productId: payload.productId ?? process.env.DEFAULT_PRODUCT_ID ?? 'lysnrai',
  };
}

export async function reviewRoutes(app: FastifyInstance) {
  app.get('/reviews', async req => {
    const access = requireAdmin(req);
    const parsed = ListReviewsQuerySchema.safeParse(req.query);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(issue => issue.message).join('; '));
    }
    return repo.list(access.productId, parsed.data);
  });

  app.post('/reviews', async req => {
    const access = requireAdmin(req);
    const parsed = CreateReviewSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(issue => issue.message).join('; '));
    }

    if (parsed.data.scope === 'workspace' && !parsed.data.workspaceId) {
      throw new BadRequestError('workspaceId is required for workspace-scoped review items');
    }

    const now = new Date().toISOString();
    const status = parsed.data.assignedTo ? 'assigned' : 'pending';
    const review: ReviewItemDoc = {
      id: `rev_${randomUUID()}`,
      productId: access.productId,
      title: parsed.data.title,
      description: parsed.data.description,
      category: parsed.data.category,
      status,
      priority: parsed.data.priority,
      scope: parsed.data.scope,
      orgId: parsed.data.orgId,
      workspaceId: parsed.data.workspaceId,
      requestedBy: access.userId,
      assignedTo: parsed.data.assignedTo,
      runId: parsed.data.runId,
      source: parsed.data.source,
      actionType: parsed.data.actionType,
      metadata: parsed.data.metadata,
      dueAt: parsed.data.dueAt,
      createdAt: now,
      updatedAt: now,
    };

    const created = await repo.create(review);
    if (created.assignedTo) {
      await notifyReviewAssigned(created, req.log);
    }
    return created;
  });

  app.get('/reviews/:id', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    return repo.getById(id, access.productId);
  });

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

    const updated = await repo.update(id, access.productId, {
      ...parsed.data,
      status: parsed.data.assignedTo && !parsed.data.status ? 'assigned' : parsed.data.status,
    });

    if (parsed.data.assignedTo) {
      await notifyReviewAssigned(updated, req.log);
    }

    return updated;
  });

  app.post('/reviews/:id/decision', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    const parsed = ReviewDecisionSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(issue => issue.message).join('; '));
    }

    const decisionStatus = parsed.data.decision;
    return repo.update(id, access.productId, {
      status: decisionStatus,
      resolution: {
        decision: decisionStatus,
        reason: parsed.data.reason,
        actedBy: access.userId,
        actedAt: new Date().toISOString(),
      },
    });
  });

  // ── Batch Decisions ─────────────────────────────────────

  app.post('/reviews/batch-decision', async req => {
    const access = requireAdmin(req);
    const body = req.body as {
      ids: string[];
      decision: 'approved' | 'rejected' | 'cancelled';
      reason?: string;
    };

    if (!body.ids || !Array.isArray(body.ids) || body.ids.length === 0) {
      throw new BadRequestError('ids array is required');
    }
    if (body.ids.length > 50) {
      throw new BadRequestError('Maximum 50 items per batch');
    }
    if (!body.decision || !['approved', 'rejected', 'cancelled'].includes(body.decision)) {
      throw new BadRequestError('Valid decision is required');
    }

    const now = new Date().toISOString();
    const results = await Promise.allSettled(
      body.ids.map(id =>
        repo.update(id, access.productId, {
          status: body.decision,
          resolution: {
            decision: body.decision,
            reason: body.reason,
            actedBy: access.userId,
            actedAt: now,
          },
        })
      )
    );

    const succeeded = results.filter(r => r.status === 'fulfilled').length;
    const failed = results.filter(r => r.status === 'rejected').length;

    return { succeeded, failed, total: body.ids.length };
  });

  // ── Flag ────────────────────────────────────────────────

  app.post('/reviews/:id/flag', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    const body = req.body as { reason?: string };

    const review = await repo.getById(id, access.productId);
    const updated = await repo.update(id, access.productId, {
      metadata: {
        ...(review.metadata ?? {}),
        flagged: true,
        flaggedBy: access.userId,
        flagReason: body.reason ?? 'Flagged by admin',
        flaggedAt: new Date().toISOString(),
      },
    });

    return updated;
  });

  // ── Delegation ──────────────────────────────────────────

  app.post('/reviews/:id/delegate', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    const body = req.body as { delegateTo: string; reason?: string };

    if (!body.delegateTo) {
      throw new BadRequestError('delegateTo is required');
    }

    const review = await repo.getById(id, access.productId);
    const updated = await repo.update(id, access.productId, {
      assignedTo: body.delegateTo,
      status: 'assigned',
      metadata: {
        ...(review.metadata ?? {}),
        delegatedFrom: review.assignedTo ?? access.userId,
        delegationReason: body.reason,
        delegatedAt: new Date().toISOString(),
      },
    });

    await notifyReviewAssigned(updated, req.log);
    return updated;
  });

  // ── Auto-Expiry ─────────────────────────────────────────

  app.post('/reviews/expire', async req => {
    const access = requireAdmin(req);
    const now = new Date().toISOString();
    const expired = await repo.listExpired(access.productId, now);

    let expiredCount = 0;
    for (const review of expired) {
      try {
        await repo.update(review.id, access.productId, {
          status: 'expired',
          resolution: {
            decision: 'expired',
            reason: 'Auto-expired: past due date',
            actedBy: 'system',
            actedAt: now,
          },
        });
        expiredCount++;
      } catch {
        // best-effort
      }
    }

    return { expired: expiredCount, checked: expired.length };
  });

  // ── Review Stats ────────────────────────────────────────

  app.get('/reviews/stats', async req => {
    const access = requireAdmin(req);
    const allReviews = await repo.listAll(access.productId);

    const byStatus: Record<string, number> = {};
    const byPriority: Record<string, number> = {};
    const byCategory: Record<string, number> = {};
    let totalDurationMs = 0;
    let resolvedCount = 0;

    for (const review of allReviews) {
      byStatus[review.status] = (byStatus[review.status] ?? 0) + 1;
      byPriority[review.priority] = (byPriority[review.priority] ?? 0) + 1;
      byCategory[review.category] = (byCategory[review.category] ?? 0) + 1;

      if (review.resolution?.actedAt) {
        const created = new Date(review.createdAt).getTime();
        const resolved = new Date(review.resolution.actedAt).getTime();
        if (resolved > created) {
          totalDurationMs += resolved - created;
          resolvedCount++;
        }
      }
    }

    const avgResolutionMs = resolvedCount > 0 ? Math.round(totalDurationMs / resolvedCount) : 0;

    return {
      total: allReviews.length,
      byStatus,
      byPriority,
      byCategory,
      avgResolutionMs,
      avgResolutionHours: Math.round((avgResolutionMs / 3600000) * 10) / 10,
      pendingCount: (byStatus['pending'] ?? 0) + (byStatus['assigned'] ?? 0),
      overdueCount: allReviews.filter(
        r =>
          r.dueAt &&
          r.dueAt < new Date().toISOString() &&
          ['pending', 'assigned'].includes(r.status)
      ).length,
    };
  });
}
