/**
 * Aggregate report reduce stage.
 *
 * Takes structured fingerprints + SQL distributions + (optional) IT Glue doc
 * titles, runs a single LLM call (Sonnet by default, Opus opt-in), and
 * produces the report's LLM-derived fields.
 *
 * Spec: docs/ticket-analyzer-phase2-spec.md → Section D.6
 */

import type Anthropic from '@anthropic-ai/sdk';
import {
  AggregateReduceResponse,
  type AggregateFingerprint,
} from '@/lib/types/analyzer';
import { callLLMStage, type LLMCallResult } from '@/lib/services/llm/call';
import { OPUS, SONNET, type ModelId } from '@/lib/services/llm/models';

const REDUCE_MAX_TOKENS = 16_000;

const SYSTEM_PROMPT = `You are a senior MSP analyst identifying patterns across multiple ticket analyses to surface systemic issues.

You will receive:
1. SQL-derived distributions (categories, clients, resolution paths, root causes)
2. An array of structured fingerprints, one per analyzed ticket
3. Optionally, a list of IT Glue documentation titles for the affected clients

Your job is to identify patterns the SQL aggregations cannot see — patterns that emerge from the gap descriptions, vendor involvement, recurrence signals, and cross-ticket clustering.

Be specific. Cite ticket numbers as evidence for every claim. Distinguish between "documentation gap exists" (no doc on this topic per the IT Glue title list) and "documentation not referenced" (doc may exist but wasn't used in resolution).

Respond ONLY with JSON matching this schema:

{
  "documentation_gaps": [
    {
      "gap": string,
      "frequency": number,
      "example_ticket_numbers": string[],
      "evidence": string,
      "itglue_check": "no_doc_exists" | "doc_exists_but_unused" | "unable_to_verify"
    }
  ],
  "process_gaps": [
    {
      "gap": string,
      "frequency": number,
      "severity": "low" | "medium" | "high",
      "example_ticket_numbers": string[],
      "evidence": string
    }
  ],
  "client_patterns": [
    {
      "client": string,
      "pattern": string,
      "frequency": number,
      "example_ticket_numbers": string[]
    }
  ],
  "recurrence_clusters": [
    {
      "theme": string,
      "ticket_numbers": string[],
      "summary": string
    }
  ],
  "systemic_observations": [
    {
      "observation": string,
      "evidence": string,
      "severity": "low" | "medium" | "high"
    }
  ],
  "recommended_actions": [
    {
      "action": string,
      "rationale": string,
      "priority": "low" | "medium" | "high",
      "type": "documentation" | "process" | "training" | "tooling"
    }
  ],
  "executive_summary": string,
  "narrative_summary": string
}

Quality bars:
- Do not list a documentation_gap unless it appears in 2+ tickets.
- Do not list a process_gap unless it appears in 2+ tickets OR has severity=high in at least one.
- Recurrence clusters require at least 2 tickets.
- Every recommended_action must be specific enough that a person could pick it up tomorrow. "Improve documentation" is rejected; "Create a runbook for AMS360 App Access Key location and integration permission verification" is acceptable.
- The narrative_summary follows the same prose formatting rules as individual analyses: markdown, no headers, no filler phrases.
- The executive_summary is 3-5 sentences, plain prose.`;

export interface AggregateReduceInput {
  distributions: {
    category_distribution: Record<string, number>;
    client_distribution: Record<string, number>;
    resolution_path_distribution: Record<string, number>;
    root_cause_distribution: Record<string, number>;
    date_range_actual: { earliest: string | null; latest: string | null };
  };
  fingerprints: Array<{
    ticket_number: string;
    fingerprint: AggregateFingerprint;
  }>;
  itglue_doc_titles?: Array<{
    client_name: string;
    doc_titles: string[];
  }>;
}

export function selectReduceModel(
  fingerprintCount: number,
  forceOpus = false,
  provider: 'anthropic' | 'openrouter' = 'anthropic'
): ModelId {
  if (provider === 'openrouter') {
    // OpenRouter side: V4 Pro for the standard reduce; R1 if forceOpus is
    // requested (extra reasoning depth, higher cost).
    if (forceOpus) return 'deepseek/deepseek-r1-0528';
    return 'deepseek/deepseek-v4-pro';
  }
  if (forceOpus) return OPUS;
  // Per spec D.6: Sonnet up to 100; Opus is opt-in. Above 25, send only the
  // structured fingerprints (no narrative excerpts) — handled at payload-build time.
  return SONNET;
}

function buildUserPayload(input: AggregateReduceInput): string {
  const parts = [
    `=== SQL DISTRIBUTIONS ===`,
    JSON.stringify(input.distributions, null, 2),
    ``,
    `=== FINGERPRINTS (${input.fingerprints.length} tickets) ===`,
    JSON.stringify(
      input.fingerprints.map((f) => ({
        ticket_number: f.ticket_number,
        ...f.fingerprint,
      })),
      null,
      2
    ),
  ];
  if (input.itglue_doc_titles && input.itglue_doc_titles.length > 0) {
    parts.push(
      ``,
      `=== IT GLUE DOC TITLES BY CLIENT ===`,
      JSON.stringify(input.itglue_doc_titles, null, 2)
    );
  } else {
    parts.push(
      ``,
      `=== IT GLUE DOC TITLES BY CLIENT ===`,
      `Not included for this report. Mark itglue_check as "unable_to_verify" for documentation_gaps.`
    );
  }
  return parts.join('\n');
}

export async function runAggregateReduceStage(
  input: AggregateReduceInput,
  options: {
    forceOpus?: boolean;
    injectedClient?: Anthropic;
    provider?: 'anthropic' | 'openrouter';
  } = {}
): Promise<LLMCallResult<AggregateReduceResponse> & { model_used: ModelId }> {
  const model = selectReduceModel(
    input.fingerprints.length,
    options.forceOpus,
    options.provider ?? 'anthropic'
  );
  const result = await callLLMStage({
    model,
    system: SYSTEM_PROMPT,
    user: buildUserPayload(input),
    schema: AggregateReduceResponse,
    maxTokens: REDUCE_MAX_TOKENS,
    client: options.injectedClient,
  });
  return { ...result, model_used: model };
}

export const _AGGREGATE_REDUCE_INTERNALS = { SYSTEM_PROMPT, REDUCE_MAX_TOKENS };
