import { fetchWithAuth } from './config'

// Path prefixes only — NOT resolved URLs. fetchWithAuth resolves the actual
// origin (relative vs. configured apiUrl) fresh on every call via
// config.ts's apiBase(), so these can't be pre-resolved once at module load
// (the config may not be known yet at import time, e.g. before Config.svelte
// or a Wails-injected __OIKOS_CONFIG__ runs).
const BASE = '/agent'
const API = '/api/v1'

// A session IS a task: a goal-structured unit of work with a lifecycle status
// and an outcome. goal/outcome/summary/entity_id are empty until the agent sets
// them (see the task-board plan). status defaults to 'active'.
export interface Session {
  id: string
  title: string
  actor: string
  goal?: string
  status?: string // active | planning | executing | awaiting_input | done | failed | abandoned
  outcome?: string // success | failure | partial
  summary?: string
  entity_id?: string
  pending_approvals?: number
  created_at: string
  last_active_at: string
}

export interface Message {
  id: string
  session_id: string
  role: string
  content: any
  created_at: string
}

export async function fetchSessions(): Promise<Session[]> {
  const res = await fetchWithAuth(`${BASE}/sessions`)
  if (!res.ok) return []
  const data = await res.json()
  return data.sessions ?? []
}

export async function fetchMessages(sessionId: string): Promise<Message[]> {
  const res = await fetchWithAuth(`${BASE}/sessions/${sessionId}`)
  if (!res.ok) return []
  const data = await res.json()
  return data.messages ?? []
}

export async function deleteSession(sessionId: string): Promise<boolean> {
  const res = await fetchWithAuth(`${BASE}/sessions/${sessionId}`, { method: 'DELETE' })
  return res.ok
}

export async function resumeSession(sessionId: string): Promise<boolean> {
  const res = await fetchWithAuth(`${BASE}/sessions/${sessionId}/resume`, { method: 'POST' })
  return res.ok
}

export interface PlanStep {
  id: string
  seq: number
  title: string
  detail: string
  status: 'pending' | 'running' | 'done' | 'failed' | 'skipped' | 'blocked' | 'replaced'
  generation?: number
  execution_id?: string
  target_slug?: string
  started_at?: string
  finished_at?: string
}

export async function fetchPlan(sessionId: string): Promise<PlanStep[]> {
  const res = await fetchWithAuth(`${BASE}/sessions/${sessionId}/plan`)
  if (!res.ok) return []
  const data = await res.json()
  return data.steps ?? []
}

export interface SessionQuestion {
  id: string
  prompt: string
  context: { why?: string; options?: string[]; entities?: string[] }
  status: 'open' | 'answered' | 'dismissed'
  answer?: string
  created_at: string
  answered_at?: string
}

export async function fetchQuestions(sessionId: string): Promise<SessionQuestion[]> {
  const res = await fetchWithAuth(`${BASE}/sessions/${sessionId}/questions`)
  if (!res.ok) return []
  const data = await res.json()
  return data.questions ?? []
}

export async function answerQuestion(sessionId: string, questionId: string, answer: string): Promise<boolean> {
  const res = await fetchWithAuth(`${BASE}/sessions/${sessionId}/questions/${questionId}/answer`, {
    method: 'POST',
    body: JSON.stringify({ answer })
  })
  return res.ok
}

export interface ChatEvent {
  type: string
  data: any
  session_id?: string
  iteration?: number
}

export function streamChat(
  message: string,
  sessionId: string | null,
  onEvent: (ev: ChatEvent) => void,
  onError: (err: string) => void,
  onDone: () => void
): AbortController {
  const controller = new AbortController()

  fetchWithAuth(`${BASE}/chat`, {
    method: 'POST',
    body: JSON.stringify({ message, session_id: sessionId ?? undefined }),
    signal: controller.signal
  }).then(async (res) => {
    if (!res.ok) {
      onError(`HTTP ${res.status}`)
      return
    }
    const reader = res.body?.getReader()
    if (!reader) {
      onError('no response body')
      return
    }
    const decoder = new TextDecoder()
    let buffer = ''

    while (true) {
      const { done, value } = await reader.read()
      if (done) break
      buffer += decoder.decode(value, { stream: true })
      const lines = buffer.split('\n')
      buffer = lines.pop() ?? ''

      for (const line of lines) {
        if (line.startsWith('data: ')) {
          try {
            const ev: ChatEvent = JSON.parse(line.slice(6))
            onEvent(ev)
          } catch {
            // skip malformed
          }
        }
      }
    }
  }).catch((err) => {
    onError(err.message)
  }).finally(() => {
    onDone()
  })

  return controller
}

export interface DashboardSummary {
  entities_by_type: Record<string, number>
  entities_by_state: Record<string, number>
  health: { healthy: number; degraded: number; down: number; unknown: number }
  signals_by_severity: Record<string, number>
  approvals_pending: number
  executions_by_state: Record<string, number>
  event_rate: { bucket: string; count: number }[]
}

export async function fetchDashboardSummary(): Promise<DashboardSummary | null> {
  const res = await fetchWithAuth(`${API}/dashboard/summary`)
  if (!res.ok) return null
  return res.json()
}

export type EntityHealth = 'healthy' | 'degraded' | 'down' | 'unknown' | 'stale'

export interface Entity {
  id: string
  slug: string
  type: string
  name: string
  state?: string | null
  attributes: Record<string, unknown>
  version: number
  created_at: string
  updated_at: string
  health?: EntityHealth | null
  last_check_at?: string | null
  maintenance_until?: string | null
}

export interface EntityFilters {
  type?: string
  state?: string
  domain?: string
  layer?: string
  q?: string
}

export async function fetchEntities(filters: EntityFilters = {}): Promise<Entity[]> {
  const params = new URLSearchParams()
  if (filters.type) params.set('type', filters.type)
  if (filters.state) params.set('state', filters.state)
  if (filters.domain) params.set('domain', filters.domain)
  if (filters.layer) params.set('layer', filters.layer)
  if (filters.q) params.set('q', filters.q)
  params.set('limit', '200')
  const res = await fetchWithAuth(`${API}/entities?${params}`)
  if (!res.ok) return []
  const data = await res.json()
  return data.items ?? []
}

export type OntologyLayer = 'meta' | 'infrastructure' | 'governance' | 'cognition'

export interface EntityType {
  name: string
  parent_type?: string | null
  is_abstract: boolean
  domain: string
  layer: OntologyLayer
  description?: string | null
}

// The graph endpoint has no layer param, so callers build a type→layer map from
// this to scope the graph client-side (the entities table filters server-side).
export async function fetchEntityTypes(): Promise<EntityType[]> {
  const res = await fetchWithAuth(`${API}/ontology`)
  if (!res.ok) return []
  const data = await res.json()
  return data.entity_types ?? []
}

export interface EventFilters {
  type?: string
  severity?: string
}

export async function fetchEvents(filters: EventFilters = {}): Promise<import('./stores/events').OikosEvent[]> {
  const params = new URLSearchParams()
  if (filters.type) params.set('type', filters.type)
  if (filters.severity) params.set('severity', filters.severity)
  params.set('limit', '100')
  const res = await fetchWithAuth(`${API}/events?${params}`)
  if (!res.ok) return []
  const data = await res.json()
  return data.items ?? []
}

export interface Approval {
  id: string
  slug: string
  subject?: string | null
  action: string
  risk_class: string
  kind: 'execution' | 'policy-change' | 'pattern-activation'
  payload?: Record<string, unknown> | null
  status: 'pending' | 'approved' | 'denied' | 'expired' | 'revoked'
  expires_at: string
  decided_at?: string | null
  decided_by?: string | null
  created_at: string
}

export async function fetchApprovals(status?: string): Promise<Approval[]> {
  const params = new URLSearchParams()
  if (status) params.set('status', status)
  params.set('limit', '200')
  const res = await fetchWithAuth(`${API}/approvals?${params}`)
  if (!res.ok) return []
  const data = await res.json()
  return data.items ?? []
}

export async function decideApproval(
  id: string,
  decision: 'approve' | 'deny' | 'revoke',
  note?: string
): Promise<Approval | null> {
  const res = await fetchWithAuth(`${API}/approvals/${id}/decision`, {
    method: 'POST',
    body: JSON.stringify({ decision, note })
  })
  if (!res.ok) return null
  return res.json()
}

export interface Execution {
  id: string
  slug: string
  target?: string | null
  action: string
  risk_class: string
  status: string
  approval_id?: string | null
  agent_id?: string | null
  result?: Record<string, unknown> | null
  duration_ms?: number | null
  verified: boolean
  correlation_id: string
  started_at?: string | null
  completed_at?: string | null
  created_at: string
}

export async function fetchExecutions(status?: string): Promise<Execution[]> {
  const params = new URLSearchParams()
  if (status) params.set('status', status)
  params.set('limit', '200')
  const res = await fetchWithAuth(`${API}/executions?${params}`)
  if (!res.ok) return []
  const data = await res.json()
  return data.items ?? []
}

export async function getExecution(id: string): Promise<Execution | null> {
  const res = await fetchWithAuth(`${API}/executions/${id}`)
  if (!res.ok) return null
  return res.json()
}

export async function cancelExecution(id: string): Promise<Execution | null> {
  const res = await fetchWithAuth(`${API}/executions/${id}/cancel`, { method: 'POST' })
  if (!res.ok) return null
  return res.json()
}

export interface ActivityItem {
  id: string
  target: string
  verb: string
  summary: string
  risk_class: string
  status: string
  duration_ms: number | null
  error?: string
  created_at: string
  completed_at: string | null
}

export async function fetchRecentActivity(limit = 50): Promise<ActivityItem[]> {
  const res = await fetchWithAuth(`${API}/activity/recent?limit=${limit}`)
  if (!res.ok) return []
  const data = await res.json()
  return data.items ?? []
}

export interface SessionDigest {
  session_id: string
  total_executions: number
  by_status: Record<string, number>
  entities_touched: string[]
  executions: { target: string; verb: string; summary: string; risk_class: string; status: string }[]
  knowledge_created: string[]
}

export async function fetchSessionDigest(sessionId: string): Promise<SessionDigest | null> {
  const res = await fetchWithAuth(`${API}/activity/session/${sessionId}`)
  if (!res.ok) return null
  return res.json()
}

export interface CapabilityTimelineItem {
  verb: string
  first_success: string | null
  successes: number
  total: number
}

export async function fetchLearningTimeline(): Promise<CapabilityTimelineItem[]> {
  const res = await fetchWithAuth(`${API}/learning/timeline`)
  if (!res.ok) return []
  const data = await res.json()
  return data.items ?? []
}

export interface TrendBucket {
  day: string
  successes: number
  failures: number
}

export async function fetchLearningTrend(): Promise<TrendBucket[]> {
  const res = await fetchWithAuth(`${API}/learning/trend`)
  if (!res.ok) return []
  const data = await res.json()
  return data.items ?? []
}

export interface Pattern {
  id: string
  slug: string
  applies_type: string
  action: string
  pattern: string
  confidence: number
  evidence_count: number
  success_count?: number
  failure_count?: number
  status: string
  quarantined?: boolean
}

export async function fetchPatterns(): Promise<Pattern[]> {
  const res = await fetchWithAuth(`${API}/patterns`)
  if (!res.ok) return []
  const data = await res.json()
  return data.items ?? []
}

export interface Skill {
  id: string
  slug: string
  name: string
  applies_type?: string | null
  action: string
  status: string
  success_rate?: number | null
  last_used_at?: string | null
}

export async function fetchSkills(): Promise<Skill[]> {
  const res = await fetchWithAuth(`${API}/skills`)
  if (!res.ok) return []
  const data = await res.json()
  return data.items ?? []
}

export interface Signal {
  id: string
  slug: string
  kind: string
  severity: 'info' | 'warning' | 'critical'
  state: 'raised' | 'acknowledged' | 'acting' | 'muted' | 'resolved' | 'failed'
  target?: string | null
  evidence?: string | null
  likely_cause?: string | null
  occurrence_count: number
  flap_count: number
  hold_down_until?: string | null
  mute_until?: string | null
  first_seen_at: string
  last_seen_at: string
}

export async function fetchSignals(filters: { state?: string; severity?: string } = {}): Promise<Signal[]> {
  const params = new URLSearchParams()
  if (filters.state) params.set('state', filters.state)
  if (filters.severity) params.set('severity', filters.severity)
  params.set('limit', '200')
  const res = await fetchWithAuth(`${API}/signals?${params}`)
  if (!res.ok) return []
  const data = await res.json()
  return data.items ?? []
}

export async function ackSignal(id: string): Promise<Signal | null> {
  const res = await fetchWithAuth(`${API}/signals/${id}/ack`, { method: 'POST' })
  if (!res.ok) return null
  return res.json()
}

export async function resolveSignal(id: string, note?: string): Promise<Signal | null> {
  const res = await fetchWithAuth(`${API}/signals/${id}/resolve`, {
    method: 'POST',
    body: JSON.stringify({ note })
  })
  if (!res.ok) return null
  return res.json()
}

export async function muteSignal(id: string, muteUntil: string, note?: string): Promise<Signal | null> {
  const res = await fetchWithAuth(`${API}/signals/${id}/mute`, {
    method: 'POST',
    body: JSON.stringify({ mute_until: muteUntil, note })
  })
  if (!res.ok) return null
  return res.json()
}

export interface Relationship {
  source: string
  target: string
  type: string
  attributes?: Record<string, unknown> | null
  valid_from: string
  valid_to?: string | null
}

export type Health = 'healthy' | 'degraded' | 'down' | 'unknown'

export interface GraphView {
  nodes: Entity[]
  edges: Relationship[]
  truncated?: boolean
  health?: Record<string, Health>
}

export interface GraphFilters {
  root?: string
  depth?: number
  relType?: string[]
  includeStatus?: boolean
}

export async function fetchGraph(filters: GraphFilters = {}): Promise<GraphView | null> {
  const params = new URLSearchParams()
  if (filters.root) params.set('root', filters.root)
  if (filters.depth) params.set('depth', String(filters.depth))
  for (const rt of filters.relType ?? []) params.append('rel_type', rt)
  if (filters.includeStatus) params.append('include', 'status')
  const res = await fetchWithAuth(`${API}/graph?${params}`)
  if (!res.ok) return null
  return res.json()
}

export interface BlastRadiusItem {
  entity: Entity
  depth: number
}

export async function fetchBlastRadius(id: string): Promise<BlastRadiusItem[]> {
  const res = await fetchWithAuth(`${API}/entities/${encodeURIComponent(id)}/blast-radius`)
  if (!res.ok) return []
  const data = await res.json()
  return data.items ?? []
}

// id is commonly a slug, not a UUID (e.g. "document:infrastructure/network") —
// slugs can contain '/', which must be percent-encoded or it splits the path
// into extra segments the router won't match.
export async function fetchEntity(id: string): Promise<Entity | null> {
  const res = await fetchWithAuth(`${API}/entities/${encodeURIComponent(id)}`)
  if (!res.ok) return null
  return res.json()
}

export interface MetricSample {
  ts: string
  value?: number | null
  avg?: number | null
  min?: number | null
  max?: number | null
}

export interface MetricSeries {
  entity_id: string
  metric: string
  rollup: 'raw' | '1h' | '1d'
  samples: MetricSample[]
}

export async function fetchMetrics(entityId: string): Promise<MetricSeries[]> {
  const params = new URLSearchParams({ entity_id: entityId, rollup: 'auto' })
  const res = await fetchWithAuth(`${API}/metrics?${params}`)
  if (!res.ok) return []
  const data = await res.json()
  return data.items ?? []
}

export interface KnowledgeHit {
  id: string
  slug: string
  type: 'document' | 'runbook' | 'investigation'
  title: string
  snippet?: string
  linked_entities?: string[]
}

export interface KnowledgeItem {
  slug: string
  title: string
  kind: 'document' | 'runbook' | 'investigation'
  source: string
  tags: string[]
  updated_at: string
  agent_authored: boolean
}

export interface RecentKnowledge {
  stats: {
    total: number
    by_kind: Record<string, number>
    agent_authored: number
    last_7d: number
  }
  items: KnowledgeItem[]
}

export async function fetchRecentKnowledge(source?: string): Promise<RecentKnowledge> {
  const params = new URLSearchParams()
  if (source) params.set('source', source)
  const res = await fetchWithAuth(`${API}/knowledge/recent?${params}`)
  if (!res.ok) return { stats: { total: 0, by_kind: {}, agent_authored: 0, last_7d: 0 }, items: [] }
  return res.json()
}

export async function fetchEntityKnowledge(entityId: string): Promise<KnowledgeHit[]> {
  const res = await fetchWithAuth(`${API}/knowledge/${entityId}`)
  if (!res.ok) return []
  const data = await res.json()
  return data.items ?? []
}

export interface KnowledgeContent {
  title: string
  content: string
  source: string
  tags: string[]
  updated_at: string
}

// Full markdown body for a document/investigation/runbook entity — distinct
// from fetchEntityKnowledge, which returns knowledge that references OTHER
// entities, not this entity's own content.
export async function fetchKnowledgeContent(id: string): Promise<KnowledgeContent | null> {
  const res = await fetchWithAuth(`${API}/knowledge/content/${encodeURIComponent(id)}`)
  if (!res.ok) return null
  return res.json()
}

export async function fetchEntityEvents(entityId: string): Promise<import('./stores/events').OikosEvent[]> {
  const params = new URLSearchParams({ entity_id: entityId, limit: '50' })
  const res = await fetchWithAuth(`${API}/events?${params}`)
  if (!res.ok) return []
  const data = await res.json()
  return data.items ?? []
}

export async function fetchEntitySignals(entityId: string): Promise<Signal[]> {
  const params = new URLSearchParams({ entity_id: entityId, limit: '50' })
  const res = await fetchWithAuth(`${API}/signals?${params}`)
  if (!res.ok) return []
  const data = await res.json()
  return data.items ?? []
}

export async function fetchEntityExecutions(entityId: string): Promise<Execution[]> {
  const params = new URLSearchParams({ target: entityId, limit: '50' })
  const res = await fetchWithAuth(`${API}/executions?${params}`)
  if (!res.ok) return []
  const data = await res.json()
  return data.items ?? []
}

export interface EntityTask {
  task: Entity
  executionCount: number
}

// Executions aren't directly browsable from an entity in a useful way — what
// matters is which task/session acted on it, and how many times. There's no
// "incoming relationships" endpoint (blast_radius/graph only walks outgoing
// edges), so this composes it client-side: find executions targeting this
// entity, then check each task's own outgoing edges (task --involves--> entity
// directly, or task --involves--> execution) to attribute them. Cheap while
// the task count is small; would want a dedicated query if that changes.
export async function fetchEntityTasks(entity: Entity): Promise<EntityTask[]> {
  const [executions, tasks] = await Promise.all([
    fetchEntityExecutions(entity.id),
    fetchEntities({ type: 'task' })
  ])
  const executionIds = new Set(executions.map((e) => e.id))

  const results = await Promise.all(
    tasks.map(async (task): Promise<EntityTask | null> => {
      const g = await fetchGraph({ root: task.slug, depth: 1 })
      if (!g) return null
      const involvesThisEntity = g.edges.some((e) => e.type === 'involves' && e.target === entity.slug)
      const nodeTypeById = new Map(g.nodes.map((n) => [n.id, n.type]))
      const idBySlug = new Map(g.nodes.map((n) => [n.slug, n.id]))
      const executionCount = g.edges.filter((e) => {
        if (e.type !== 'involves') return false
        const targetId = idBySlug.get(e.target)
        return targetId != null && nodeTypeById.get(targetId) === 'execution' && executionIds.has(targetId)
      }).length
      if (!involvesThisEntity && executionCount === 0) return null
      return { task, executionCount }
    })
  )
  return results.filter((r): r is EntityTask => r !== null)
}

export interface Check {
  id: string
  slug: string
  kind: 'http' | 'tcp' | 'disk' | 'cert-expiry' | 'drift' | 'ping' | 'ssh-script'
  target?: string | null
  target_type?: string | null
  config?: Record<string, unknown>
  interval_s: number
  timeout_s: number
  zone?: string | null
  enabled: boolean
  version: number
}

export async function fetchChecksForTarget(targetSlug: string): Promise<Check[]> {
  const params = new URLSearchParams({ target: targetSlug, limit: '50' })
  const res = await fetchWithAuth(`${API}/checks?${params}`)
  if (!res.ok) return []
  const data = await res.json()
  return data.items ?? []
}

export async function patchCheck(id: string, version: number, patch: { enabled?: boolean; interval_s?: number; timeout_s?: number }): Promise<Check | null> {
  const res = await fetchWithAuth(`${API}/checks/${id}`, {
    method: 'PATCH',
    headers: { 'If-Match': `"${version}"` },
    body: JSON.stringify(patch)
  })
  if (!res.ok) return null
  return res.json()
}

export interface AgentActivity {
  id: number
  ts: string
  agent_id: string
  session_id?: string | null
  activity_type: 'tool_call' | 'reasoning' | 'decision' | 'mcp_query' | 'escalation'
  tool_name?: string | null
  entity_id?: string | null
  input_summary?: string | null
  output_summary?: string | null
  duration_ms?: number | null
  token_count?: number | null
  success?: boolean | null
  correlation_id?: string | null
}

export async function fetchAgentActivity(filters: {
  agent_id?: string
  activity_type?: string
  entity_id?: string
  limit?: number
} = {}): Promise<AgentActivity[]> {
  const params = new URLSearchParams()
  if (filters.agent_id) params.set('agent_id', filters.agent_id)
  if (filters.activity_type) params.set('activity_type', filters.activity_type)
  if (filters.entity_id) params.set('entity_id', filters.entity_id)
  params.set('limit', String(filters.limit ?? 200))
  const res = await fetchWithAuth(`${API}/agent-activity?${params}`)
  if (!res.ok) return []
  const data = await res.json()
  return data.items ?? []
}

export async function searchKnowledge(q: string, limit = 50): Promise<KnowledgeHit[]> {
  const params = new URLSearchParams({ q, limit: String(limit) })
  const res = await fetchWithAuth(`${API}/knowledge/search?${params}`)
  if (!res.ok) return []
  const data = await res.json()
  return data.items ?? []
}

export interface AuditEntry {
  id: number
  ts: string
  actor_type: 'agent' | 'operator' | 'system' | 'scheduler'
  actor_id?: string | null
  action: string
  entity_id?: string | null
  method?: string | null
  path?: string | null
  status_code?: number | null
  detail?: Record<string, unknown>
  source_ip?: string | null
  correlation_id?: string | null
}

export async function fetchAudit(filters: {
  actor_type?: string
  actor_id?: string
  entity_id?: string
  action?: string
  correlation_id?: string
  limit?: number
} = {}): Promise<AuditEntry[]> {
  const params = new URLSearchParams()
  if (filters.actor_type) params.set('actor_type', filters.actor_type)
  if (filters.actor_id) params.set('actor_id', filters.actor_id)
  if (filters.entity_id) params.set('entity_id', filters.entity_id)
  if (filters.action) params.set('action', filters.action)
  if (filters.correlation_id) params.set('correlation_id', filters.correlation_id)
  params.set('limit', String(filters.limit ?? 200))
  const res = await fetchWithAuth(`${API}/audit?${params}`)
  if (!res.ok) return []
  const data = await res.json()
  return data.items ?? []
}
