/**
 * POST /api/analyzer/tickets/:ticketNumber/analyze-bundle
 *
 * Body: { linkedTicketNumbers: string[], includeItglueContext?, reportTitle?, confirmedCost? }
 *
 * Behavior:
 *   1. Validate the master + each linked ticket exists in the local mirror.
 *   2. For each ticket: idempotency-check via content hash; if a fresh
 *      analysis exists, collect its id; otherwise queue an analyzer job.
 *   3. Cost-guard the *new* work only (already-complete analyses don't add
 *      cost).
 *   4. Create an analyzer_aggregate_reports row populated with
 *      expected_ticket_numbers + triggered_by_ticket_number. If everything
 *      was already complete, transition straight to 'pending' and fire
 *      runAggregateReport. Otherwise the row sits in 'pending_analyses'
 *      until the worker chain-trigger flips it once all jobs land.
 *
 * Response:
 *   { aggregateReportId, ticketCount, queuedJobIds, alreadyCompleteAnalysisIds, status }
 */

import { NextRequest, NextResponse } from 'next/server';
import { requireAuth } from '@/lib/auth-utils';
import postgresClient from '@/lib/services/postgres-client';
import {
  loadTicketBundle,
  TicketNotFoundError,
} from '@/lib/services/analyzer/data-access';
import { preprocessTicket } from '@/lib/services/analyzer/preprocessor';
import {
  findExistingAnalysisByContentHash,
  queueJob,
} from '@/lib/services/analyzer/persistence';
import {
  createAggregateReport,
  runAggregateReport,
} from '@/lib/services/analyzer/aggregate-persistence';
import {
  estimateAggregateReportCost,
  evaluateCost,
  recordCostAuditDecision,
} from '@/lib/services/analyzer/cost-guard';
import { BundleAnalyzeRequest } from '@/lib/types/analyzer';
// Side-effect import: ensure the worker self-starts so queued jobs run.
import '@/lib/services/analyzer/worker';

const MAX_BUNDLE_SIZE = 25;

/**
 * Pessimistic per-ticket cost. Anthropic path runs Sonnet (+ optional Opus);
 * OpenRouter path runs DeepSeek V4 Pro (+ optional R1) at ~7-10× lower rate.
 */
const PER_TICKET_COST_USD: Record<'anthropic' | 'openrouter', number> = {
  anthropic: 0.15,
  openrouter: 0.02,
};

export async function POST(
  request: NextRequest,
  { params }: { params: Promise<{ ticketNumber: string }> }
) {
  const { session, error } = await requireAuth();
  if (error) return error;

  const { ticketNumber: masterTicketNumber } = await params;

  const body = await request.json().catch(() => ({}));
  const parsed = BundleAnalyzeRequest.safeParse(body);
  if (!parsed.success) {
    return NextResponse.json(
      { error: 'Invalid request body', details: parsed.error.issues },
      { status: 400 }
    );
  }
  const {
    linkedTicketNumbers,
    includeItglueContext,
    reportTitle,
    confirmedCost,
    provider,
  } = parsed.data;

  // Build the unique ordered set: master first, then linked (deduped, master removed).
  const linkedSet = new Set(linkedTicketNumbers);
  linkedSet.delete(masterTicketNumber);
  const allTicketNumbers = [masterTicketNumber, ...Array.from(linkedSet)];

  if (allTicketNumbers.length < 2) {
    return NextResponse.json(
      {
        error:
          'Bundle requires at least one linked ticket besides the master. Use /analyze for single-ticket runs.',
      },
      { status: 400 }
    );
  }
  if (allTicketNumbers.length > MAX_BUNDLE_SIZE) {
    return NextResponse.json(
      {
        error: `Bundle size ${allTicketNumbers.length} exceeds cap ${MAX_BUNDLE_SIZE}.`,
      },
      { status: 400 }
    );
  }

  // Verify every ticket exists locally in one query.
  const lookup = await postgresClient.query<{ ticket_number: string }>(
    `SELECT ticket_number FROM tickets
       WHERE ticket_number = ANY($1::text[])
         AND COALESCE(is_deleted, false) = false`,
    [allTicketNumbers]
  );
  const present = new Set(lookup.rows.map((r) => r.ticket_number));
  const missing = allTicketNumbers.filter((n) => !present.has(n));
  if (missing.length > 0) {
    return NextResponse.json(
      { error: 'Some tickets not found in local mirror', missing },
      { status: 404 }
    );
  }

  const userId = (session?.user as { id: string } | undefined)?.id ?? null;

  // Per-ticket idempotency check + queue plan.
  const queuedJobIds: string[] = [];
  const alreadyCompleteAnalysisIds: string[] = [];
  const ticketsNeedingAnalysis: string[] = [];

  for (const tn of allTicketNumbers) {
    let bundle;
    try {
      bundle = await loadTicketBundle(tn);
    } catch (err) {
      if (err instanceof TicketNotFoundError) {
        // Shouldn't happen — we just verified existence — but be defensive.
        return NextResponse.json(
          { error: `Ticket ${tn} disappeared between checks` },
          { status: 404 }
        );
      }
      console.error(`[analyze-bundle] data-access error for ${tn}:`, err);
      return NextResponse.json(
        { error: 'Failed to load ticket', message: tn },
        { status: 500 }
      );
    }

    const pre = preprocessTicket(bundle);
    const existing = await findExistingAnalysisByContentHash(
      tn,
      pre.content_hash,
      provider
    );
    if (existing) {
      alreadyCompleteAnalysisIds.push(existing.id);
    } else {
      ticketsNeedingAnalysis.push(tn);
    }
  }

  // Cost-guard: only the new per-ticket work + the aggregate-reduce step.
  const aggregateCost = estimateAggregateReportCost({
    ticketCount: allTicketNumbers.length,
    includeItglueContext,
  });
  const newPerTicketCost =
    ticketsNeedingAnalysis.length * PER_TICKET_COST_USD[provider];
  const estimatedCost =
    Math.round((newPerTicketCost + aggregateCost) * 10_000) / 10_000;

  const evaluation = await evaluateCost({
    userId,
    estimatedCost,
    confirmedCost,
  });
  await recordCostAuditDecision({
    userId,
    action: 'analyze_bundle',
    evaluation,
    context: {
      masterTicketNumber,
      ticketCount: allTicketNumbers.length,
      newPerTicketCount: ticketsNeedingAnalysis.length,
      includeItglueContext,
    },
  });
  if (evaluation.decision === 'blocked') {
    return NextResponse.json(
      {
        error: 'Daily cost limit reached',
        message: evaluation.decisionReason,
        estimatedCost: evaluation.estimatedCost,
        dailySpendBefore: evaluation.dailySpendBefore,
      },
      { status: 403 }
    );
  }
  if (evaluation.decision === 'requires_confirmation') {
    return NextResponse.json(
      {
        error: 'Confirmation required',
        message: evaluation.decisionReason,
        estimatedCost: evaluation.estimatedCost,
        dailySpendBefore: evaluation.dailySpendBefore,
        requiresConfirmation: true,
        retryWith: { confirmedCost: true },
      },
      { status: 400 }
    );
  }

  // Queue jobs for the missing tickets.
  for (const tn of ticketsNeedingAnalysis) {
    const job = await queueJob({
      ticket_number: tn,
      queued_by_user_id: userId,
      provider,
    });
    queuedJobIds.push(job.id);
  }

  // Create the aggregate report row. Bundle mode (expectedTicketNumbers set)
  // means status starts as 'pending_analyses' if any jobs were queued, or as
  // 'pending' if everything was already complete and we can run immediately.
  const allAlreadyComplete = ticketsNeedingAnalysis.length === 0;
  const created = await createAggregateReport({
    generatedByUserId: userId,
    filterCriteria: {
      mode: 'bundle',
      masterTicketNumber,
      linkedTicketNumbers: Array.from(linkedSet),
      provider,
    },
    analysisIds: alreadyCompleteAnalysisIds,
    ticketCount: allTicketNumbers.length,
    includeItglueContext,
    reportTitle: reportTitle ?? null,
    expectedTicketNumbers: allAlreadyComplete ? undefined : allTicketNumbers,
    triggeredByTicketNumber: masterTicketNumber,
  });

  if (allAlreadyComplete) {
    void runAggregateReport(created.id).catch((err) => {
      console.error('[analyze-bundle] background runner threw:', err);
    });
  }

  return NextResponse.json({
    aggregateReportId: created.id,
    ticketCount: allTicketNumbers.length,
    queuedJobIds,
    alreadyCompleteAnalysisIds,
    status: allAlreadyComplete ? 'pending' : 'pending_analyses',
    estimatedCost: evaluation.estimatedCost,
    softWarn: evaluation.softWarn,
  });
}
