/**
 * Zod schemas + types for the AI Ticket Analyzer feature.
 *
 * Every LLM JSON response is parsed through one of these schemas. On parse
 * failure the pipeline retries once, then marks the job failed and stores the
 * raw response in analyzer_jobs.error_message.
 *
 * Spec: docs/wulf-pulse-ticket-analyzer-prompt.md
 */

import { z } from 'zod';

// =============================================================================
// Shared enums
// =============================================================================

export const ActorType = z.enum([
  'wulf_tech',
  'client_contact',
  'vendor',
  'system',
  'automation',
]);
export type ActorType = z.infer<typeof ActorType>;

export const EventSource = z.enum([
  'ticket_create',
  'ticket_note',
  'time_entry',
  'status_change',
  'resolution',
]);
export type EventSource = z.infer<typeof EventSource>;

export const Visibility = z.enum([
  'customer_facing',
  'internal_only',
  'mixed', // time entry with both Summary Notes and Internal Notes
]);
export type Visibility = z.infer<typeof Visibility>;

export const Severity = z.enum(['low', 'medium', 'high']);
export type Severity = z.infer<typeof Severity>;

export const ComplexityTier = z.enum(['low', 'medium', 'high']);
export type ComplexityTier = z.infer<typeof ComplexityTier>;

export const TicketType = z.enum([
  'incident',
  'service_request',
  'problem',
  'change',
  'other',
]);
export type TicketType = z.infer<typeof TicketType>;

// =============================================================================
// Stage 0 — Pre-processor output: a tagged event in the unified timeline.
// =============================================================================
//
// Per the spec, a single Time Entry with both Summary Notes and Internal Notes
// is rendered as ONE event with both fields preserved (visibility = 'mixed'),
// not two separate events.

export const TaggedEvent = z.object({
  timestamp: z.string().datetime({ offset: true }),
  actor: z.string(),
  actor_type: ActorType,
  source: EventSource,
  visibility: Visibility,
  summary_notes: z.string().optional(),
  internal_notes: z.string().optional(),
  hours: z.number().nonnegative().optional(),
});
export type TaggedEvent = z.infer<typeof TaggedEvent>;

// =============================================================================
// Stage 1 — Triage (Haiku) response.
// =============================================================================

export const TriageResponse = z.object({
  ticket_type: TicketType,
  category: z.string(),
  entities: z.object({
    client_name: z.string().nullable(),
    site_name: z.string().nullable(),
    devices: z.array(z.string()),
    users: z.array(z.string()),
    applications: z.array(z.string()),
    vendors: z.array(z.string()),
  }),
  is_resolved: z.boolean(),
  status_matches_reality: z.boolean(),
  complexity_tier: ComplexityTier,
  complexity_reasons: z.array(z.string()),
  itglue_lookup_needed: z.boolean(),
  itglue_search_hints: z.array(z.string()),
});
export type TriageResponse = z.infer<typeof TriageResponse>;

// =============================================================================
// Stage 3 — Deep analysis (Sonnet) response.
// =============================================================================

export const TimelineEntry = z.object({
  timestamp: z.string().datetime({ offset: true }),
  actor: z.string(),
  actor_type: ActorType,
  source: EventSource,
  visibility: Visibility,
  action: z.string(),
});
export type TimelineEntry = z.infer<typeof TimelineEntry>;

export const Gap = z.object({
  description: z.string(),
  severity: Severity,
  evidence_timestamps: z.array(z.string().datetime({ offset: true })),
});
export type Gap = z.infer<typeof Gap>;

export const ITGlueDocReference = z.object({
  id: z.string(),
  name: z.string(),
  url: z.string().url(),
  doc_type: z.string(),
  relevance_reason: z.string(),
});
export type ITGlueDocReference = z.infer<typeof ITGlueDocReference>;

export const DeepAnalysisResponse = z.object({
  summary: z.string(),
  timeline: z.array(TimelineEntry),
  what_was_done: z.array(z.string()),
  what_should_have_been_done: z.array(z.string()),
  gaps: z.array(Gap),
  next_step: z.string(),
  next_step_rationale: z.string(),
  post_resolution_analysis: z.string().nullable(),
  confidence_score: z.number().min(0).max(1),
  needs_human_review: z.boolean(),
  human_review_reasons: z.array(z.string()),
  ambiguities_for_opus: z.array(z.string()),
  itglue_docs_referenced: z.array(ITGlueDocReference),
});
export type DeepAnalysisResponse = z.infer<typeof DeepAnalysisResponse>;

// =============================================================================
// Stage 4 — Deep reasoning (Opus) response.
// Updates are a subset of the Sonnet response's mutable fields.
// =============================================================================

export const OpusUpdates = z
  .object({
    next_step: z.string(),
    next_step_rationale: z.string(),
    gaps: z.array(Gap),
    confidence_score: z.number().min(0).max(1),
    needs_human_review: z.boolean(),
    human_review_reasons: z.array(z.string()),
    post_resolution_analysis: z.string().nullable(),
  })
  .partial();
export type OpusUpdates = z.infer<typeof OpusUpdates>;

export const OpusResponse = z.object({
  opus_notes: z.string(),
  updates: OpusUpdates,
});
export type OpusResponse = z.infer<typeof OpusResponse>;

// =============================================================================
// Persisted analysis row — shape returned by GET /api/analyzer/analyses/:id.
// Mirrors the analyzer_analyses table.
// =============================================================================

export const AnalysisStatus = z.enum(['pending', 'running', 'complete', 'failed']);
export type AnalysisStatus = z.infer<typeof AnalysisStatus>;

export const HumanReviewReasons = z.array(z.string());

export const PersistedAnalysis = z.object({
  id: z.string().uuid(),
  ticketNumber: z.string(),
  autotaskTicketId: z.number().int(),
  analysisVersion: z.number().int().positive(),
  contentHashAtAnalysis: z.string(),
  triggeredByUserId: z.string().nullable(),
  triggeredAt: z.string().datetime({ offset: true }),
  status: AnalysisStatus,
  completedAt: z.string().datetime({ offset: true }).nullable(),

  haikuUsed: z.boolean(),
  sonnetUsed: z.boolean(),
  opusUsed: z.boolean(),
  totalInputTokens: z.number().int().nonnegative(),
  totalOutputTokens: z.number().int().nonnegative(),
  estimatedCostUsd: z.number().nonnegative(),

  summary: z.string().nullable(),
  timeline: z.array(TimelineEntry).nullable(),
  whatWasDone: z.array(z.string()).nullable(),
  whatShouldHaveBeenDone: z.array(z.string()).nullable(),
  gaps: z.array(Gap).nullable(),
  nextStep: z.string().nullable(),
  nextStepRationale: z.string().nullable(),
  postResolutionAnalysis: z.string().nullable(),
  confidenceScore: z.number().min(0).max(1).nullable(),
  needsHumanReview: z.boolean(),
  humanReviewReasons: HumanReviewReasons.nullable(),

  itglueDocsReferenced: z.array(ITGlueDocReference),

  filteredNoiseCount: z.number().int().nonnegative(),
  errorMessage: z.string().nullable(),

  /** Phase 3: which LLM provider produced this analysis. */
  provider: z.enum(['anthropic', 'openrouter']).default('anthropic'),
});
export type PersistedAnalysis = z.infer<typeof PersistedAnalysis>;

// =============================================================================
// Job status row — shape returned by GET /api/analyzer/jobs/:jobId.
// Mirrors the analyzer_jobs table.
// =============================================================================

export const JobStatus = z.enum([
  'queued',
  'fetching',
  'triaging',
  'itglue',
  'analyzing',
  'deep_review',
  'complete',
  'failed',
]);
export type JobStatus = z.infer<typeof JobStatus>;

export const AnalyzerJob = z.object({
  id: z.string().uuid(),
  ticketNumber: z.string(),
  queuedByUserId: z.string().nullable(),
  status: JobStatus,
  resultAnalysisId: z.string().uuid().nullable(),
  queuedAt: z.string().datetime({ offset: true }),
  startedAt: z.string().datetime({ offset: true }).nullable(),
  finishedAt: z.string().datetime({ offset: true }).nullable(),
  errorMessage: z.string().nullable(),
});
export type AnalyzerJob = z.infer<typeof AnalyzerJob>;

// =============================================================================
// Phase 2 — Stage 6 fingerprint schema (cross-ticket aggregation).
// =============================================================================

export const RootCauseClass = z.enum([
  'configuration_drift',
  'user_error',
  'vendor_issue',
  'hardware_failure',
  'documentation_gap',
  'process_gap',
  'unknown',
  'other',
]);
export type RootCauseClass = z.infer<typeof RootCauseClass>;

export const ResolutionPath = z.enum([
  'resolved_by_wulf',
  'resolved_by_vendor',
  'resolved_by_client',
  'unresolved',
  'self_resolved_before_wulf_action',
]);
export type ResolutionPath = z.infer<typeof ResolutionPath>;

export const FingerprintConfidence = z.enum(['low', 'medium', 'high']);
export type FingerprintConfidence = z.infer<typeof FingerprintConfidence>;

export const AggregateFingerprint = z.object({
  category: z.string(),
  subcategories: z.array(z.string()),
  ticket_type_inferred: z.string(),
  root_cause_class: RootCauseClass,

  client_name: z.string(),
  vendors_involved: z.array(z.string()),
  applications_involved: z.array(z.string()),
  device_classes: z.array(z.string()),

  wulf_actions_taken: z.array(z.string()),
  vendor_cases_opened: z.number().int().nonnegative(),
  resolution_path: ResolutionPath,

  documentation_gaps_observed: z
    .array(
      z.object({
        description: z.string(),
        confidence: FingerprintConfidence,
      })
    )
    .max(5),
  process_gaps_observed: z
    .array(
      z.object({
        description: z.string(),
        severity: Severity,
      })
    )
    .max(5),

  similar_to_signals: z.array(z.string()),
  tags: z.array(z.string()),

  generated_by_model: z.string(),
  generated_at: z.string().datetime({ offset: true }),
});
export type AggregateFingerprint = z.infer<typeof AggregateFingerprint>;

// =============================================================================
// Phase 2 — Stage execution row (per-stage I/O persistence).
// =============================================================================

export const StageName = z.enum([
  'preprocess',
  'triage',
  'itglue',
  'analyze',
  'deep_review',
  'fingerprint',
]);
export type StageName = z.infer<typeof StageName>;

/**
 * In-memory shape used by the pipeline to record per-stage I/O. The pipeline
 * pushes one of these into the worker's array via the onStageRecord callback,
 * and the worker bulk-inserts them after the analyzer_analyses row exists
 * (success OR failure).
 */
export interface StageExecutionRecord {
  stage: StageName;
  stage_order: number;
  model_id: string | null;
  input_payload: unknown;
  output_payload: unknown;
  input_tokens: number | null;
  output_tokens: number | null;
  latency_ms: number | null;
  started_at: Date;
  completed_at: Date;
  error_message: string | null;
}

export const StageExecution = z.object({
  id: z.string().uuid(),
  analysisId: z.string().uuid(),
  stage: StageName,
  stageOrder: z.number().int().positive(),
  modelId: z.string().nullable(),
  inputPayload: z.unknown(),
  outputPayload: z.unknown(),
  inputTokens: z.number().int().nullable(),
  outputTokens: z.number().int().nullable(),
  latencyMs: z.number().int().nullable(),
  startedAt: z.string().datetime({ offset: true }),
  completedAt: z.string().datetime({ offset: true }),
  errorMessage: z.string().nullable(),
});
export type StageExecution = z.infer<typeof StageExecution>;

// =============================================================================
// Phase 2.6 — Aggregate report (reduce step) schemas.
// =============================================================================

export const ITGlueCheck = z.enum([
  'no_doc_exists',
  'doc_exists_but_unused',
  'unable_to_verify',
]);
export type ITGlueCheck = z.infer<typeof ITGlueCheck>;

export const RecommendedActionType = z.enum([
  'documentation',
  'process',
  'training',
  'tooling',
]);
export type RecommendedActionType = z.infer<typeof RecommendedActionType>;

export const AggregateReduceResponse = z.object({
  documentation_gaps: z.array(
    z.object({
      gap: z.string(),
      frequency: z.number().int().nonnegative(),
      example_ticket_numbers: z.array(z.string()),
      evidence: z.string(),
      itglue_check: ITGlueCheck,
    })
  ),
  process_gaps: z.array(
    z.object({
      gap: z.string(),
      frequency: z.number().int().nonnegative(),
      severity: Severity,
      example_ticket_numbers: z.array(z.string()),
      evidence: z.string(),
    })
  ),
  client_patterns: z.array(
    z.object({
      client: z.string(),
      pattern: z.string(),
      frequency: z.number().int().nonnegative(),
      example_ticket_numbers: z.array(z.string()),
    })
  ),
  recurrence_clusters: z.array(
    z.object({
      theme: z.string(),
      ticket_numbers: z.array(z.string()),
      summary: z.string(),
    })
  ),
  systemic_observations: z.array(
    z.object({
      observation: z.string(),
      evidence: z.string(),
      severity: Severity,
    })
  ),
  recommended_actions: z.array(
    z.object({
      action: z.string(),
      rationale: z.string(),
      priority: Severity,
      type: RecommendedActionType,
    })
  ),
  executive_summary: z.string(),
  narrative_summary: z.string(),
});
export type AggregateReduceResponse = z.infer<typeof AggregateReduceResponse>;

export const AggregateReportStatus = z.enum([
  'pending',
  'pending_analyses',
  'running',
  'complete',
  'failed',
]);
export type AggregateReportStatus = z.infer<typeof AggregateReportStatus>;

// =============================================================================
// API request bodies.
// =============================================================================

export const ProviderEnum = z.enum(['anthropic', 'openrouter']);
export type ProviderEnum = z.infer<typeof ProviderEnum>;

export const AnalyzeTicketRequest = z.object({
  force: z.boolean().optional().default(false),
  provider: ProviderEnum.optional().default('anthropic'),
});
export type AnalyzeTicketRequest = z.infer<typeof AnalyzeTicketRequest>;

export const ShareAnalysisRequest = z.object({
  recipientEmail: z.string().email(),
  note: z.string().max(2000).optional(),
});
export type ShareAnalysisRequest = z.infer<typeof ShareAnalysisRequest>;

// =============================================================================
// Phase 3 — link discovery & bundle analysis.
// =============================================================================

export const LinkConfidence = z.enum(['high', 'medium']);
export type LinkConfidence = z.infer<typeof LinkConfidence>;

export const LinkSource = z.enum([
  'related_tickets_section',
  'description_mention',
  'note_mention',
  'problem_ticket_id',
  'llm_suggested',
]);
export type LinkSource = z.infer<typeof LinkSource>;

export const TicketRef = z.object({
  ticket_number: z.string(),
  title: z.string().nullable(),
  status_label: z.string().nullable(),
  last_activity_date: z.string().datetime({ offset: true }).nullable(),
  source: LinkSource,
  confidence: LinkConfidence,
  reason: z.string().nullable(),
});
export type TicketRef = z.infer<typeof TicketRef>;

export const DiscoveredLinks = z.object({
  explicit: z.array(TicketRef),
  suggested: z.array(TicketRef),
  isProblemTicket: z.boolean(),
  problemTicketSignals: z.array(z.string()),
});
export type DiscoveredLinks = z.infer<typeof DiscoveredLinks>;

export const BundleAnalyzeRequest = z.object({
  linkedTicketNumbers: z.array(z.string()).max(50),
  includeItglueContext: z.boolean().optional().default(true),
  reportTitle: z.string().max(200).nullable().optional(),
  confirmedCost: z.boolean().optional().default(false),
  provider: ProviderEnum.optional().default('anthropic'),
});
export type BundleAnalyzeRequest = z.infer<typeof BundleAnalyzeRequest>;

export const SuggestLinksRequest = z.object({
  includeSuggested: z.boolean().optional().default(true),
});
export type SuggestLinksRequest = z.infer<typeof SuggestLinksRequest>;

// =============================================================================
// Phase 4 — IT Glue asset audit (LLM-driven gap analysis vs ticket history).
// =============================================================================

export const AuditConfidence = z.enum(['high', 'medium', 'low']);
export type AuditConfidence = z.infer<typeof AuditConfidence>;

export const AssetAuditFieldGap = z.object({
  field_name: z.string(),
  why_missing_matters: z.string(),
  suggested_value: z.string().nullable(),
  evidence_ticket_numbers: z.array(z.string()),
  confidence: AuditConfidence,
});
export type AssetAuditFieldGap = z.infer<typeof AssetAuditFieldGap>;

export const AssetAuditNotePromotion = z.object({
  quoted_note_text: z.string(),
  target_field: z.string(),
  suggested_value: z.string(),
  confidence: AuditConfidence,
});
export type AssetAuditNotePromotion = z.infer<typeof AssetAuditNotePromotion>;

export const AssetAuditContradiction = z.object({
  description: z.string(),
  evidence: z.string(),
});
export type AssetAuditContradiction = z.infer<typeof AssetAuditContradiction>;

export const AssetAuditResponse = z.object({
  field_gaps: z.array(AssetAuditFieldGap),
  notes_promotions: z.array(AssetAuditNotePromotion),
  contradictions: z.array(AssetAuditContradiction),
  overall_score: z.number().min(0).max(1),
});
export type AssetAuditResponse = z.infer<typeof AssetAuditResponse>;

export const RunAssetAuditRequest = z.object({
  provider: ProviderEnum.optional().default('anthropic'),
});
export type RunAssetAuditRequest = z.infer<typeof RunAssetAuditRequest>;

export const ApplyAssetSuggestionRequest = z.object({
  auditId: z.string().uuid(),
  fieldName: z.string(),
  suggestedValue: z.unknown(),
  /** ticket_numbers + gap description for the source_evidence row. */
  sourceEvidence: z
    .object({
      ticket_numbers: z.array(z.string()).optional(),
      gap_description: z.string().optional(),
    })
    .optional(),
});
export type ApplyAssetSuggestionRequest = z.infer<typeof ApplyAssetSuggestionRequest>;

// =============================================================================
// Internal pipeline payload — passed between stages, NOT a wire format.
// =============================================================================

export const TicketHeaderForLLM = z.object({
  ticket_number: z.string(),
  autotask_ticket_id: z.number().int(),
  title: z.string(),
  status_label: z.string(),
  priority_label: z.string().nullable(),
  queue: z.string().nullable(),
  account_name: z.string().nullable(),
  contact_name: z.string().nullable(),
  contact_email: z.string().nullable(),
  created_at: z.string().datetime({ offset: true }),
  resolved_at: z.string().datetime({ offset: true }).nullable(),
});
export type TicketHeaderForLLM = z.infer<typeof TicketHeaderForLLM>;

export const PreprocessedTicket = z.object({
  header: TicketHeaderForLLM,
  events: z.array(TaggedEvent),
  counts: z.object({
    total_events: z.number().int().nonnegative(),
    customer_facing: z.number().int().nonnegative(),
    internal_only: z.number().int().nonnegative(),
    mixed: z.number().int().nonnegative(),
    filtered_noise: z.number().int().nonnegative(),
  }),
  content_hash: z.string(),
});
export type PreprocessedTicket = z.infer<typeof PreprocessedTicket>;
