import { describe, it, expect, vi } from 'vitest';
import { readFileSync } from 'fs';
import { resolve } from 'path';
import {
  buildTriageUserPayload,
  runTriageStage,
  _STAGE1_INTERNALS,
} from './stage1-triage';
import { preprocessTicket, type RawTicketBundle } from '../preprocessor';
import type Anthropic from '@anthropic-ai/sdk';

const FIXTURE_DIR = resolve(__dirname, '..', 'fixtures');
const inputFixture = JSON.parse(
  readFileSync(`${FIXTURE_DIR}/T20260424.0045.input.json`, 'utf8')
) as RawTicketBundle;

const HAIKU_VALID_TRIAGE = JSON.stringify({
  ticket_type: 'service_request',
  category: 'Vendor Integration',
  entities: {
    client_name: 'Seubert and Associates',
    site_name: null,
    devices: [],
    users: ['Lorentz Hinrichsen', 'Tyler Lyster', 'Cory Houck'],
    applications: ['AMS360', 'ImageRight', 'VSSO', 'Outmarket AI'],
    vendors: ['Vertafore', 'Outmarket AI'],
  },
  is_resolved: false,
  status_matches_reality: false,
  complexity_tier: 'medium',
  complexity_reasons: [
    'Multiple vendors involved (Vertafore, Outmarket AI)',
    'Customer indicated they could proceed independently but additional work was logged afterward',
  ],
  itglue_lookup_needed: true,
  itglue_search_hints: [
    'AMS360 App Access Key',
    'ImageRight integration',
    'VSSO Managed Users',
  ],
});

function makeFakeClient(
  responses: string[]
): { fake: Anthropic; bodies: any[] } {
  const bodies: any[] = [];
  let i = 0;
  const create = vi.fn(async (body: any) => {
    bodies.push(body);
    const text = responses[i++];
    if (text === undefined) throw new Error('No more queued responses');
    return {
      id: `msg_${i}`,
      type: 'message',
      role: 'assistant',
      content: [{ type: 'text', text }],
      model: body.model,
      stop_reason: 'end_turn',
      stop_sequence: null,
      usage: {
        input_tokens: 5_000,
        output_tokens: 500,
        cache_creation_input_tokens: 0,
        cache_read_input_tokens: 0,
      },
    } as Anthropic.Message;
  });
  return {
    fake: { messages: { create } } as unknown as Anthropic,
    bodies,
  };
}

describe('buildTriageUserPayload', () => {
  it('includes the ticket header, counts, and chronological events', () => {
    const pre = preprocessTicket(inputFixture);
    const { payload } = buildTriageUserPayload(pre);

    expect(payload).toContain('=== TICKET HEADER ===');
    expect(payload).toContain('=== EVENT COUNTS ===');
    expect(payload).toContain('=== TAGGED EVENTS (chronological) ===');
    expect(payload).toContain('T20260424.0045');
    expect(payload).toContain('Waiting Customer');
    expect(payload).toContain('Seubert and Associates');
    // Lorentz's note content survived through to the prompt.
    expect(payload).toContain('take it from here');
  });

  it('drops zero events when payload is well under the cap', () => {
    const pre = preprocessTicket(inputFixture);
    const { events_dropped } = buildTriageUserPayload(pre);
    expect(events_dropped).toBe(0);
  });

  it('drops oldest internal-only events when payload exceeds the cap', () => {
    const pre = preprocessTicket(inputFixture);
    const filler = 'X'.repeat(8_000);
    // Inject internal_only events with bulky content until we exceed the cap.
    pre.events.push(
      ...Array.from({ length: 8 }, (_, i) => ({
        timestamp: `2026-04-2${(i % 9) + 1}T00:00:00.000Z`,
        actor: 'Filler',
        actor_type: 'wulf_tech' as const,
        source: 'time_entry' as const,
        visibility: 'internal_only' as const,
        internal_notes: filler,
      }))
    );
    const { events_dropped, payload } = buildTriageUserPayload(pre);
    expect(events_dropped).toBeGreaterThan(0);
    expect(payload.length).toBeLessThanOrEqual(_STAGE1_INTERNALS.PAYLOAD_CHAR_CAP + 200);
    // Customer-facing events must still be present (Lorentz's pivot note).
    expect(payload).toContain('take it from here');
  });
});

describe('runTriageStage', () => {
  it('returns a parsed TriageResponse from a valid first-attempt JSON reply', async () => {
    const pre = preprocessTicket(inputFixture);
    const { fake, bodies } = makeFakeClient([HAIKU_VALID_TRIAGE]);

    const result = await runTriageStage(pre, fake);

    expect(result.attempts).toBe(1);
    expect(result.data.complexity_tier).toBe('medium');
    expect(result.data.itglue_lookup_needed).toBe(true);
    expect(result.data.entities.vendors).toContain('Vertafore');
    expect(result.estimated_cost_usd).toBeGreaterThan(0);

    // Verify the request used Haiku, the canonical system prompt, and our cap.
    expect(bodies[0].model).toBe('claude-haiku-4-5');
    expect(bodies[0].max_tokens).toBe(_STAGE1_INTERNALS.STAGE1_MAX_TOKENS);
    expect(bodies[0].system[0].text).toBe(_STAGE1_INTERNALS.SYSTEM_PROMPT);
    expect(bodies[0].system[0].cache_control).toEqual({ type: 'ephemeral' });
  });

  it('retries on bad first response and returns the corrected payload', async () => {
    const pre = preprocessTicket(inputFixture);
    const { fake } = makeFakeClient([
      'not json',
      HAIKU_VALID_TRIAGE,
    ]);
    const result = await runTriageStage(pre, fake);
    expect(result.attempts).toBe(2);
    expect(result.data.is_resolved).toBe(false);
  });
});
