/**
 * AI Observability — AITrace type, in-memory store, and dev-mode debug output.
 *
 * Every AI service call (real or mock) records a trace automatically.
 * In development the store is also mirrored to localStorage and exposed
 * as window.__aiTraces for DevTools inspection.
 *
 * Reading traces in code:
 *   import { aiTraceStore } from '../tracing'
 *   const traces = aiTraceStore.getAll()
 *
 * Reading in DevTools (dev only):
 *   window.__aiTraces.getAll()
 *   window.__aiTraces.clear()
 */

import type { AIProvenance } from './IAIService'

// ── Types ─────────────────────────────────────────────────────────────────────

/** How the AI response was produced and whether it was valid. */
export type AITraceValidationStatus =
  | 'valid'           // AI responded; Zod schema passed
  | 'invalid_schema'  // AI responded but Zod validation failed → fallback triggered
  | 'fallback'        // No API key or pre-flight failure → mock used immediately
  | 'api_error'       // HTTP error from provider (4xx / 5xx)
  | 'network_error'   // Network or timeout failure

/** Reason category for non-successful responses. */
export type AITraceErrorType =
  | 'no_api_key'
  | 'network'
  | 'timeout'
  | 'api_error'
  | 'schema_validation'
  | 'unknown'

/**
 * One recorded AI call. Every field is required except errorType (only present
 * when responseValidationStatus indicates a failure) and inputSizeChars.
 */
export interface AITrace {
  /** Unique per-call ID (crypto.randomUUID) */
  id: string
  /** IAIService method name — e.g. 'parseNeed', 'classifyMarketSignal' */
  method: string
  provider: AIProvenance['provider']
  model: string
  promptVersion: string
  /** End-to-end wall-clock time including simulated delay for mock calls */
  latencyMs: number
  /** True when a mock response was substituted for a failed AI call */
  fallbackUsed: boolean
  /** True when the Zod schema validation of the raw AI response passed */
  validationPassed: boolean
  responseValidationStatus: AITraceValidationStatus
  /** Only present when responseValidationStatus indicates a failure */
  errorType?: AITraceErrorType
  /** Human-readable reason for the fallback — mirrors AIProvenance.fallbackReason */
  fallbackReason?: string
  source: AIProvenance['source']
  /** ISO-8601 timestamp of when the call completed */
  createdAt: string
  /** Character count of the input sent to the model (never stores prompt text) */
  inputSizeChars?: number
}

// ── Store ─────────────────────────────────────────────────────────────────────

function percentile(sortedArr: number[], p: number): number {
  if (sortedArr.length === 0) return 0
  return sortedArr[Math.max(0, Math.ceil(p * sortedArr.length) - 1)]
}

const MAX_ENTRIES  = 100
const STORAGE_KEY  = 'pm_ai_traces'

class AITraceStore {
  private entries: AITrace[] = []

  add(trace: AITrace): void {
    this.entries = [trace, ...this.entries].slice(0, MAX_ENTRIES)

    if (import.meta.env.DEV) {
      this.debugLog(trace)
      this.persistToStorage(trace)
    }
  }

  getAll(): AITrace[] {
    return [...this.entries]
  }

  /** Return the N most recent traces. */
  getLast(n: number): AITrace[] {
    return this.entries.slice(0, n)
  }

  /** Remove all in-memory and stored traces. */
  clear(): void {
    this.entries = []
    if (import.meta.env.DEV) {
      try { localStorage.removeItem(STORAGE_KEY) } catch { /* ignore */ }
    }
  }

  /** Summary stats for the Ops dashboard or DevTools. */
  stats(): {
    total: number
    fallbacks: number
    schemaFailures: number
    avgLatencyMs: number
    latencyPercentiles: { p50: number; p90: number; p99: number }
    byMethod: Record<string, number>
    failureCountByError: Record<string, number>
    validationFailuresByMethod: Record<string, number>
    fallbackReasonDistribution: Record<string, number>
    promptVersionUsage: Record<string, number>
  } {
    const total          = this.entries.length
    const fallbacks      = this.entries.filter(t => t.fallbackUsed).length
    const schemaFailures = this.entries.filter(t => t.responseValidationStatus === 'invalid_schema').length

    const sortedLatencies = [...this.entries.map(t => t.latencyMs)].sort((a, b) => a - b)
    const avgLatencyMs    = total === 0 ? 0 : Math.round(sortedLatencies.reduce((s, l) => s + l, 0) / total)

    const byMethod = this.entries.reduce<Record<string, number>>((acc, t) => {
      acc[t.method] = (acc[t.method] ?? 0) + 1
      return acc
    }, {})

    const failureCountByError = this.entries
      .filter(t => t.errorType)
      .reduce<Record<string, number>>((acc, t) => {
        acc[t.errorType!] = (acc[t.errorType!] ?? 0) + 1
        return acc
      }, {})

    const validationFailuresByMethod = this.entries
      .filter(t => t.responseValidationStatus === 'invalid_schema')
      .reduce<Record<string, number>>((acc, t) => {
        acc[t.method] = (acc[t.method] ?? 0) + 1
        return acc
      }, {})

    const fallbackReasonDistribution = this.entries
      .filter(t => t.fallbackReason)
      .reduce<Record<string, number>>((acc, t) => {
        acc[t.fallbackReason!] = (acc[t.fallbackReason!] ?? 0) + 1
        return acc
      }, {})

    const promptVersionUsage = this.entries.reduce<Record<string, number>>((acc, t) => {
      acc[t.promptVersion] = (acc[t.promptVersion] ?? 0) + 1
      return acc
    }, {})

    return {
      total,
      fallbacks,
      schemaFailures,
      avgLatencyMs,
      latencyPercentiles: {
        p50: percentile(sortedLatencies, 0.50),
        p90: percentile(sortedLatencies, 0.90),
        p99: percentile(sortedLatencies, 0.99),
      },
      byMethod,
      failureCountByError,
      validationFailuresByMethod,
      fallbackReasonDistribution,
      promptVersionUsage,
    }
  }

  /** Load the persisted trace list from localStorage (dev only). */
  loadFromStorage(): AITrace[] {
    if (!import.meta.env.DEV) return []
    try {
      const raw = localStorage.getItem(STORAGE_KEY)
      return raw ? (JSON.parse(raw) as AITrace[]) : []
    } catch {
      return []
    }
  }

  private debugLog(trace: AITrace): void {
    const icon =
      trace.responseValidationStatus === 'valid'                               ? '✅' :
      trace.responseValidationStatus === 'invalid_schema'                      ? '⚠️' :
      trace.responseValidationStatus === 'fallback'                            ? '🔁' : '❌'

    const fallback = trace.fallbackUsed ? ' [FALLBACK]' : ''
    const validation = trace.validationPassed ? '' : ' schema:FAIL'
    console.debug(
      `[AITrace] ${icon} ${trace.method}${fallback}${validation}` +
      ` — ${trace.provider}/${trace.model}` +
      ` | ${trace.latencyMs}ms | source:${trace.source}` +
      (trace.fallbackReason ? ` | reason:${trace.fallbackReason}` : ''),
      trace,
    )
  }

  private persistToStorage(trace: AITrace): void {
    try {
      const prev = this.loadFromStorage()
      localStorage.setItem(
        STORAGE_KEY,
        JSON.stringify([trace, ...prev].slice(0, MAX_ENTRIES)),
      )
    } catch { /* ignore QuotaExceededError */ }
  }
}

export const aiTraceStore = new AITraceStore()

// Expose in DevTools for debugging (dev only):
//   window.__aiTraces.getAll()   → all traces
//   window.__aiTraces.stats()    → summary statistics
//   window.__aiTraces.clear()    → clear all
if (import.meta.env.DEV && typeof window !== 'undefined') {
  ;(window as unknown as Record<string, unknown>).__aiTraces = aiTraceStore
}

// ── Helpers ───────────────────────────────────────────────────────────────────

/**
 * Derive the AITraceValidationStatus from an AIResponse provenance after a
 * successful (non-throwing) withFallback call.
 */
export function provenanceToStatus(
  fallbackUsed: boolean,
  source: AIProvenance['source'],
  fallbackReason?: string,
): AITraceValidationStatus {
  if (!fallbackUsed) return 'valid'
  if (fallbackReason === 'no_api_key') return 'fallback'
  if (fallbackReason?.startsWith('api_error')) return 'api_error'
  if (fallbackReason?.startsWith('network')) return 'network_error'
  if (source === 'mock') return 'invalid_schema'
  return 'valid'
}
