/**
 * Stage 3 — Sonnet deep analysis.
 *
 * Takes the pre-processed ticket, the Stage 1 triage metadata, and (optionally)
 * the redacted IT Glue snippets, and produces the full structured analysis:
 * timeline, what-was-done / should-have-been-done, gaps, next-step, confidence,
 * IT-Glue references, and a list of ambiguities for Stage 4 (Opus) to resolve.
 *
 * Spec: docs/wulf-pulse-ticket-analyzer-prompt.md → "Stage 3 — Deep Analysis (Sonnet)"
 */

import {
  DeepAnalysisResponse,
  type PreprocessedTicket,
  type TriageResponse,
} from '@/lib/types/analyzer';
import { callLLMStage, type LLMCallResult } from '@/lib/services/llm/call';
import { SONNET, type ModelId } from '@/lib/services/llm/models';
import type { RedactedDoc } from '@/lib/services/analyzer/itglue-search';
import type Anthropic from '@anthropic-ai/sdk';

const STAGE3_MAX_TOKENS = 16_000;
const PAYLOAD_CHAR_CAP = 80_000;

const SYSTEM_PROMPT = `You are a senior MSP technician at Wulf Consulting reviewing a ticket. You will receive:
1. The full ticket with all retained notes and time entries (already filtered for workflow noise; tagged with visibility markers — customer_facing, internal_only, or mixed)
2. Triage metadata from a previous pass
3. Optionally, sanitized IT Glue documentation snippets for the client

Be specific and reference events by their timestamp and actor. Do not invent facts. If something is unclear, say so explicitly and add it to ambiguities_for_opus.

Pay particular attention to these patterns, which are common failure modes:
- The customer indicates they have resolved the issue or want to take it over, but work continues afterward
- The Autotask status does not match the actual state (e.g. "Waiting Customer" when the customer has already responded, or "In Progress" with no recent activity)
- The original ask in the requester's first message is different from what the ticket pivoted to addressing
- Internal notes contradict or add important context missing from customer-facing summary notes
- A vendor case was opened but the customer's direct ask could have been answered without it
- Time was billed for work the customer didn't ultimately need

Tag actor_type by the email domain we already classified for you (provided in the input), not by what the message says. Notes from \`@wulfconsulting.com\` addresses are internal communications regardless of tone.

Respond ONLY with JSON. No prose, no code fences. Schema:
{
  "summary": string,                    // markdown, 2-4 sentences, neutral status briefing
  "timeline": [
    {
      "timestamp": string,
      "actor": string,
      "actor_type": "wulf_tech" | "client_contact" | "vendor" | "system" | "automation",
      "source": "ticket_create" | "ticket_note" | "time_entry" | "status_change" | "resolution",
      "visibility": "customer_facing" | "internal_only" | "mixed",
      "action": string
    }
  ],
  "what_was_done": string[],
  "what_should_have_been_done": string[],
  "gaps": [
    { "description": string, "severity": "low" | "medium" | "high", "evidence_timestamps": string[] }
  ],
  "next_step": string,                  // markdown, single concrete action; may use
                                        // **bold** for the action verb and bullet
                                        // sub-steps if multi-part
  "next_step_rationale": string,        // markdown, 1-2 short paragraphs; if there are
                                        // 3+ competing considerations, use a bulleted list
  "post_resolution_analysis": string | null,  // markdown, same conventions as above
  "confidence_score": number,
  "needs_human_review": boolean,
  "human_review_reasons": string[],
  "ambiguities_for_opus": string[],
  "itglue_docs_referenced": [
    { "id": string, "name": string, "url": string, "doc_type": string, "relevance_reason": string }
  ]
}

Formatting rules for prose fields (summary, next_step, next_step_rationale,
post_resolution_analysis):

- Output is markdown and will be rendered with a markdown renderer. Use **bold**
  for emphasis on key terms or actions. Use *italics* sparingly for client-facing
  language being quoted. Use bullet lists for enumerable items.

- Do NOT use markdown headers (#, ##, ###). These fields render inside cards that
  already have their own headings.

- For summary: write as a neutral status briefing a manager could read in 10
  seconds. Lead with the most important fact. Plain language. No hedging.

- For next_step: state the concrete action in the first sentence, with the action
  verb in **bold**. If there are sub-steps, follow with a bulleted list. Address
  the action to the technician, not the customer.

- For next_step_rationale: open with one short sentence stating the core reason.
  Follow with a short paragraph elaborating, OR a bulleted list if there are 3+
  distinct considerations. If there's a competing alternative worth noting, name
  it explicitly: "Considered X but rejected because..."

- Avoid filler phrases. Banned openings: "It's worth noting that...", "It's
  important to understand...", "Based on the available information...",
  "After reviewing the ticket...". Get to the point.

Set needs_human_review = true if any of:
- confidence_score < 0.6
- gaps contain any "high" severity item
- ticket open >7 days with no clear resolution path
- conflicting information between notes
- billed hours appear excessive for the work performed

post_resolution_analysis must be null when triage's is_resolved is false.

Only include itglue_docs_referenced you actually used. Use the doc id, name, and doc_type verbatim from the input. If a doc had no URL, use an empty string for url.`;

export interface DeepAnalysisInput {
  pre: PreprocessedTicket;
  triage: TriageResponse;
  itglue_docs?: RedactedDoc[];
}

export function buildDeepAnalysisUserPayload(input: DeepAnalysisInput): {
  payload: string;
  events_dropped: number;
} {
  const headerJson = JSON.stringify(input.pre.header, null, 2);
  const triageJson = JSON.stringify(input.triage, null, 2);
  const itglueJson = JSON.stringify(input.itglue_docs ?? [], null, 2);
  const events = input.pre.events.slice();

  let droppedNotice = '';
  let eventsJson = JSON.stringify(events, null, 2);
  let droppedCount = 0;

  while (
    headerJson.length +
      triageJson.length +
      itglueJson.length +
      eventsJson.length >
    PAYLOAD_CHAR_CAP
  ) {
    const oldestInternalIdx = events.findIndex(
      (e) => e.visibility === 'internal_only'
    );
    if (oldestInternalIdx === -1) break;
    events.splice(oldestInternalIdx, 1);
    droppedCount += 1;
    eventsJson = JSON.stringify(events, null, 2);
  }
  if (droppedCount > 0) {
    droppedNotice = `\n\nNOTE: ${droppedCount} oldest internal-only event(s) were dropped to fit the size cap. All customer-facing events are preserved.`;
  }

  const payload = [
    `=== TICKET HEADER ===`,
    headerJson,
    ``,
    `=== TRIAGE METADATA (Stage 1) ===`,
    triageJson,
    ``,
    `=== TAGGED EVENTS (chronological) ===`,
    eventsJson,
    ``,
    `=== IT GLUE DOCUMENTATION (sanitized) ===`,
    itglueJson,
    droppedNotice,
  ].join('\n');

  return { payload, events_dropped: droppedCount };
}

export interface DeepAnalysisStageResult extends LLMCallResult<DeepAnalysisResponse> {
  events_dropped: number;
}

export async function runDeepAnalysisStage(
  input: DeepAnalysisInput,
  injectedClient?: Anthropic,
  modelOverride?: ModelId
): Promise<DeepAnalysisStageResult> {
  const { payload, events_dropped } = buildDeepAnalysisUserPayload(input);
  const result = await callLLMStage({
    model: modelOverride ?? SONNET,
    system: SYSTEM_PROMPT,
    user: payload,
    schema: DeepAnalysisResponse,
    maxTokens: STAGE3_MAX_TOKENS,
    client: injectedClient,
  });
  return { ...result, events_dropped };
}

export const _STAGE3_INTERNALS = {
  SYSTEM_PROMPT,
  PAYLOAD_CHAR_CAP,
  STAGE3_MAX_TOKENS,
};
