/**
 * GET /api/phishing/campaigns/[id]
 *   Returns a single campaign with nested linked reports, messages,
 *   indicators, classification history, remediation actions, audit trail,
 *   a fresh blast-radius lookup, and a merged chronological timeline —
 *   everything the Phase 22 review page needs in one call.
 */

import { NextRequest, NextResponse } from 'next/server';
import { requirePermission } from '@/lib/auth-utils';
import postgresClient from '@/lib/services/postgres-client';
import { getBlastRadius, type BlastRadiusResult } from '@/lib/services/mimecast-blast-radius';
import { getMimecastClientForTenant } from '@/lib/services/mimecast-client';
import { mergeTimeline } from '@/lib/services/phishing-timeline';

const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;

interface CampaignRow {
  id: string;
  campaign_key: string | null;
  group_method: string | null;
  first_seen_at: string | null;
  last_seen_at: string | null;
  report_count: number;
  status: string;
  created_at: string;
  updated_at: string;
}

interface ReportRow {
  id: string;
  ticket_id: string;
  ticket_number: string | null;
  company_id: string | null;
  company_name: string | null;
  title: string | null;
  created_at: string;
  requester_email: string | null;
}

interface MessageRow {
  id: string;
  report_id: string;
  message_id: string | null;
  subject: string | null;
  headers: unknown;
  urls: unknown;
  attachments: unknown;
  body_preview: string | null;
}

interface IndicatorRow {
  id: string;
  message_id: string;
  indicator_type: string;
  value: string;
  metadata: unknown;
}

interface ClassificationRow {
  id: string;
  verdict: string;
  confidence: string | null;
  summary: string | null;
  reasons: unknown;
  recommended_actions: unknown;
  requires_approval: boolean;
  created_at: string;
}

interface RemediationActionRow {
  id: string;
  action_type: string;
  status: string;
  params: unknown;
  approved_by: string | null;
  approved_at: string | null;
}

interface AuditEventRow {
  id: string;
  actor: string | null;
  event_type: string;
  payload: unknown;
  created_at: string;
}

interface MimecastTenantRow {
  client_id: string;
  client_secret: string;
  base_url: string | null;
}

export async function GET(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { error } = await requirePermission('phishing', 'read');
  if (error) return error;

  const { id } = await params;
  // V5: validate UUID shape before querying — a malformed UUID would otherwise
  // surface as an unhandled Postgres error -> uncaught 500.
  if (!UUID_RE.test(id)) {
    return NextResponse.json({ error: 'Invalid campaign id' }, { status: 400 });
  }

  try {
    const campaignRes = await postgresClient.query<CampaignRow>(
      `SELECT id::text, campaign_key, group_method, first_seen_at::text,
              last_seen_at::text, report_count, status, created_at::text, updated_at::text
         FROM campaigns WHERE id = $1`,
      [id]
    );
    const campaign = campaignRes.rows[0];
    if (!campaign) {
      return NextResponse.json({ error: 'Campaign not found' }, { status: 404 });
    }

    // Bulk-fetch linked reports (+ join contacts for requester email — campaigns
    // has no recipients column, must be derived via this join).
    const reportsRes = await postgresClient.query<ReportRow>(
      `SELECT r.id::text, r.ticket_id::text, r.ticket_number, r.company_id::text,
              r.company_name, r.title, r.created_at::text,
              c.email_address AS requester_email
         FROM reports r
         LEFT JOIN contacts c ON c.id = r.requester_contact_id
        WHERE r.campaign_id = $1
        ORDER BY r.created_at ASC`,
      [id]
    );
    const reportIds = reportsRes.rows.map((r) => r.id);

    // Bulk-fetch messages keyed by the report-id array. messages has no
    // subject column — subject lives in headers JSONB. Widened to also
    // return the full evidence shapes (headers/urls/attachments/body_preview)
    // for the review page's evidence card.
    const messagesRes = reportIds.length
      ? await postgresClient.query<MessageRow>(
          `SELECT id::text, report_id::text, message_id, headers->>'subject' AS subject,
                  headers, urls, attachments, body_preview
             FROM messages WHERE report_id = ANY($1::uuid[])`,
          [reportIds]
        )
      : { rows: [] as MessageRow[] };
    const messageIds = messagesRes.rows.map((m) => m.id);

    // Bulk-fetch indicators keyed by the message-id array.
    const indicatorsRes = messageIds.length
      ? await postgresClient.query<IndicatorRow>(
          `SELECT id::text, message_id::text, indicator_type, value, metadata
             FROM indicators WHERE message_id = ANY($1::uuid[])`,
          [messageIds]
        )
      : { rows: [] as IndicatorRow[] };

    // Classifications — widened to include reasons/recommended_actions/
    // requires_approval for the review page's classification card.
    const classificationsRes = await postgresClient.query<ClassificationRow>(
      `SELECT id::text, verdict, confidence, summary, reasons, recommended_actions,
              requires_approval, created_at::text
         FROM classifications WHERE campaign_id = $1 ORDER BY created_at DESC`,
      [id]
    );

    // Remediation actions proposed/approved/executed for this campaign.
    const remediationRes = await postgresClient.query<RemediationActionRow>(
      `SELECT id::text, action_type, status, params, approved_by, approved_at::text
         FROM remediation_actions WHERE campaign_id = $1 ORDER BY created_at ASC`,
      [id]
    );

    // Audit trail — approvals, completions, false-positive markings.
    const auditRes = await postgresClient.query<AuditEventRow>(
      `SELECT id::text, actor, event_type, payload, created_at::text
         FROM audit_events WHERE campaign_id = $1 ORDER BY created_at ASC`,
      [id]
    );

    const reports = reportsRes.rows.map((r) => ({
      id: r.id,
      ticketId: r.ticket_id,
      ticketNumber: r.ticket_number,
      companyId: r.company_id,
      companyName: r.company_name,
      title: r.title,
      createdAt: r.created_at,
      requesterEmail: r.requester_email,
    }));

    const messages = messagesRes.rows.map((m) => ({
      id: m.id,
      reportId: m.report_id,
      messageId: m.message_id,
      subject: m.subject,
      headers: m.headers,
      urls: m.urls,
      attachments: m.attachments,
      bodyPreview: m.body_preview,
    }));

    const indicators = indicatorsRes.rows.map((i) => ({
      id: i.id,
      messageId: i.message_id,
      indicatorType: i.indicator_type,
      value: i.value,
      metadata: i.metadata,
    }));

    const classifications = classificationsRes.rows.map((c) => ({
      id: c.id,
      verdict: c.verdict,
      confidence: c.confidence,
      summary: c.summary,
      reasons: c.reasons,
      recommendedActions: c.recommended_actions,
      requiresApproval: c.requires_approval,
      createdAt: c.created_at,
    }));

    // Derive `completedAt` for each remediation action from the audit trail
    // (remediation_actions has no dedicated completion-timestamp column) —
    // the 'remediation_completed' audit event's payload.actionId points back
    // at the action it completed.
    const completedAtByActionId = new Map<string, string>();
    for (const event of auditRes.rows) {
      if (event.event_type === 'remediation_completed') {
        const actionId = (event.payload as { actionId?: string } | null)?.actionId;
        if (actionId) completedAtByActionId.set(actionId, event.created_at);
      }
    }

    const remediationActions = remediationRes.rows.map((a) => ({
      id: a.id,
      actionType: a.action_type,
      status: a.status,
      params: a.params,
      approvedBy: a.approved_by,
      approvedAt: a.approved_at,
      completedAt: completedAtByActionId.get(a.id) ?? null,
    }));

    const auditEvents = auditRes.rows.map((e) => ({
      id: e.id,
      actor: e.actor,
      eventType: e.event_type,
      payload: e.payload,
      createdAt: e.created_at,
    }));

    // Fresh blast-radius lookup per request (D-03: never persisted here).
    // Sender/recipient/subject/dateWindow derivation copied verbatim from
    // lib/services/campaign-classifier.ts's gatherCampaignEvidence — NOT
    // triage-note-service.ts's empty-string call (Pitfall 3), which would
    // produce an unscoped Mimecast fan-out.
    const primaryReport = reports[0] ?? null;
    let blastRadius: BlastRadiusResult;
    if (primaryReport) {
      const primaryMessage = messages.find((m) => m.reportId === primaryReport.id) ?? null;
      const senderIndicator = indicators.find(
        (i) => i.messageId === primaryMessage?.id && i.indicatorType === 'sender'
      );
      const messageHeaders = (primaryMessage?.headers ?? null) as
        | { from?: { email?: string | null } | null }
        | null;
      const createdAt = new Date(primaryReport.createdAt);

      // Bug 2 (D-05): resolve the reporting company's own registered Mimecast
      // tenant, if one exists, and query it directly instead of the global
      // env-configured (Wulf) tenant. Falls back to the global client when
      // the company has no enabled mimecast_tenants row.
      let tenantOptions: { client: ReturnType<typeof getMimecastClientForTenant>; cacheScope: string } | undefined;
      if (primaryReport.companyId) {
        const tenantRes = await postgresClient.query<MimecastTenantRow>(
          `SELECT client_id, client_secret, base_url
             FROM mimecast_tenants
            WHERE company_id = $1 AND enabled = true
            ORDER BY id LIMIT 1`,
          [primaryReport.companyId]
        );
        const tenantRow = tenantRes.rows[0];
        if (tenantRow) {
          tenantOptions = {
            client: getMimecastClientForTenant({
              client_id: tenantRow.client_id,
              client_secret: tenantRow.client_secret,
              base_url: tenantRow.base_url ?? undefined,
            }),
            cacheScope: primaryReport.companyId,
          };
        }
      }

      blastRadius = await getBlastRadius(
        {
          sender: senderIndicator?.value ?? messageHeaders?.from?.email ?? '',
          recipient: primaryReport.requesterEmail ?? '',
          subject: primaryMessage?.subject ?? primaryReport.title ?? '',
          dateWindow: {
            start: new Date(createdAt.getTime() - 24 * 60 * 60 * 1000),
            // Bug 1: clamp the end of the window to now — a freshly-detected
            // campaign's primary report is <24h old, which would otherwise
            // produce a future `end` that Mimecast's real API rejects
            // (err_track_and_trace_invalid_end_date), swallowed internally by
            // searchDeliveredMessages() as a false zero-count "clean" result.
            end: new Date(Math.min(createdAt.getTime() + 24 * 60 * 60 * 1000, Date.now())),
          },
        },
        tenantOptions
      );
    } else {
      blastRadius = { status: 'unavailable', reason: 'not_configured' };
    }

    // Merged chronological timeline (reports + classifications + audit
    // events), oldest first.
    const timeline = mergeTimeline(
      reports.map((r) => ({
        createdAt: r.createdAt,
        reportId: r.id,
        ticketNumber: r.ticketNumber,
        companyName: r.companyName,
      })),
      classifications,
      auditEvents
    );

    return NextResponse.json({
      id: campaign.id,
      campaignKey: campaign.campaign_key,
      groupMethod: campaign.group_method,
      firstSeenAt: campaign.first_seen_at,
      lastSeenAt: campaign.last_seen_at,
      reportCount: campaign.report_count,
      status: campaign.status,
      createdAt: campaign.created_at,
      updatedAt: campaign.updated_at,
      reports,
      messages,
      indicators,
      classifications,
      remediationActions,
      auditEvents,
      blastRadius,
      timeline,
    });
  } catch (err) {
    console.error('[PHISHING-CAMPAIGN-DETAIL] Failed to load campaign', id, err);
    return NextResponse.json(
      { error: 'Failed to load campaign', message: err instanceof Error ? err.message : 'Unknown error' },
      { status: 500 }
    );
  }
}
