/**
 * EvidenceStore - Create and list evidence references per DD §11.2
 *
 * Implements EvidenceStore contract (contracts §14).
 * - create: ingest evidence.created
 * - list_for_entity(entity_type, entity_id) — NOT list_for_task
 *
 * Backed by SQLite via bun:sqlite for persistent storage.
 *
 * @module packages/runtime/src/artifacts/EvidenceStore
 */

import { randomUUID } from 'crypto'
import { Database } from 'bun:sqlite'

import type {
  EvidenceRefID,
  EvidenceRef,
  EvidenceCreateInput,
  EvidenceStore as IEvidenceStore,
  SessionID,
  TaskID,
  AgentID,
  ToolRunID,
  CommandRunID,
  ArtifactID,
  ISOTimeString,
  RuntimeEvent,
} from '@aircoding/contracts'

import { EventIngestor } from '../events/EventIngestor.js'

interface EvidenceRecord {
  evidence_ref_id: EvidenceRefID
  session_id: SessionID
  kind: string
  ref: string
  claim: string
  location_json?: unknown
  task_id?: TaskID
  agent_id?: AgentID
  tool_run_id?: ToolRunID
  command_run_id?: CommandRunID
  artifact_id?: ArtifactID
  diagnostic_id?: string
  message_id?: string
  created_at: ISOTimeString
}

/**
 * EvidenceStore implements the EvidenceStore contract per DD §11.2.
 * Uses SQLite for persistent storage instead of in-memory Map.
 */
export class EvidenceStore implements IEvidenceStore {
  private sessionId: SessionID
  private eventIngestor: EventIngestor
  private db: Database

  constructor(sessionId: SessionID, db: Database, eventIngestor?: EventIngestor) {
    this.sessionId = sessionId
    this.db = db
    this.eventIngestor = eventIngestor ?? new EventIngestor()
    this.initSchema()
  }

  /**
   * Initialize the evidence_refs table and apply PRAGMAs.
   */
  initSchema(): void {
    this.db.exec('PRAGMA journal_mode = WAL')
    this.db.exec('PRAGMA synchronous = NORMAL')

    this.db.exec(`
      CREATE TABLE IF NOT EXISTS evidence_refs (
        evidence_ref_id TEXT PRIMARY KEY,
        session_id TEXT NOT NULL,
        kind TEXT NOT NULL,
        ref TEXT NOT NULL,
        claim TEXT NOT NULL,
        location_json TEXT,
        task_id TEXT,
        agent_id TEXT,
        tool_run_id TEXT,
        command_run_id TEXT,
        artifact_id TEXT,
        diagnostic_id TEXT,
        message_id TEXT,
        created_at TEXT NOT NULL
      )
    `)
  }

  async create(input: EvidenceCreateInput): Promise<EvidenceRef> {
    const evidenceRefId = this.generateEvidenceRefId()

    const now = new Date().toISOString() as ISOTimeString

    const record: EvidenceRecord = {
      evidence_ref_id: evidenceRefId,
      session_id: this.sessionId,
      kind: input.kind,
      ref: input.ref,
      claim: input.claim,
      location_json: input.location_json,
      task_id: input.task_id,
      agent_id: input.agent_id,
      tool_run_id: input.tool_run_id,
      command_run_id: input.command_run_id,
      artifact_id: input.artifact_id,
      diagnostic_id: input.diagnostic_id,
      message_id: input.message_id,
      created_at: now,
    }

    await this.ingestEvidenceCreated(record)

    const locationJsonStr = record.location_json != null
      ? JSON.stringify(record.location_json)
      : null

    this.db.run(
      `INSERT INTO evidence_refs (
        evidence_ref_id, session_id, kind, ref, claim, location_json,
        task_id, agent_id, tool_run_id, command_run_id, artifact_id,
        diagnostic_id, message_id, created_at
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
      record.evidence_ref_id,
      record.session_id,
      record.kind,
      record.ref,
      record.claim,
      locationJsonStr,
      record.task_id ?? null,
      record.agent_id ?? null,
      record.tool_run_id ?? null,
      record.command_run_id ?? null,
      record.artifact_id ?? null,
      record.diagnostic_id ?? null,
      record.message_id ?? null,
      record.created_at
    )

    return {
      evidence_ref_id: evidenceRefId,
      kind: input.kind,
      ref: input.ref,
      claim: input.claim,
      location_json: input.location_json,
    }
  }

  async list_for_entity(entity_type: string, entity_id: string): Promise<EvidenceRef[]> {
    const columnMap: Record<string, string> = {
      task: 'task_id',
      agent: 'agent_id',
      tool_run: 'tool_run_id',
      command_run: 'command_run_id',
      artifact: 'artifact_id',
      diagnostic: 'diagnostic_id',
      message: 'message_id',
    }

    const column = columnMap[entity_type]
    if (!column) {
      return []
    }

    const rows = this.db.query(
      `SELECT * FROM evidence_refs WHERE ${column} = ?`
    ).all(entity_id) as any[]

    const results: EvidenceRef[] = []
    for (const row of rows) {
      results.push({
        evidence_ref_id: row.evidence_ref_id,
        kind: row.kind,
        ref: row.ref,
        claim: row.claim,
        location_json: row.location_json ? JSON.parse(row.location_json) : undefined,
      })
    }

    return results
  }

  private generateEvidenceRefId(): EvidenceRefID {
    return `evi_${randomUUID().replace(/-/g, '').slice(0, 24)}` as EvidenceRefID
  }

  private async ingestEvidenceCreated(record: EvidenceRecord): Promise<void> {
    const payload = {
      evidence_ref_id: record.evidence_ref_id,
      kind: record.kind,
      ref: record.ref,
      location_json: record.location_json,
      claim: record.claim,
      task_id: record.task_id,
      agent_id: record.agent_id,
      tool_run_id: record.tool_run_id,
      command_run_id: record.command_run_id,
      artifact_id: record.artifact_id,
      diagnostic_id: record.diagnostic_id,
      message_id: record.message_id,
    }

    const event: RuntimeEvent<typeof payload> = {
      id: `evt_${randomUUID().replace(/-/g, '').slice(0, 24)}` as any,
      type: 'evidence.created',
      version: 1,
      timestamp: record.created_at,
      session_id: this.sessionId,
      source: {
        kind: 'system',
      },
      route: ['evidence', 'created'],
      payload,
    }

    await this.eventIngestor.ingest(event)
  }
}

export function createEvidenceStore(
  sessionId: SessionID,
  db: Database,
  eventIngestor?: EventIngestor
): EvidenceStore {
  return new EvidenceStore(sessionId, db, eventIngestor)
}
