/**
 * DiagnosticRepository - CRUD + list_by_signature, list_by_command_run for diagnostics table (§14)
 *
 * Implements Repository<DiagnosticRecord, DiagnosticInsert, DiagnosticUpdate> per contracts §6.
 *
 * @module packages/runtime/src/storage/repositories/DiagnosticRepository
 */

import type {
  Repository,
  TransactionHandle,
  SessionID,
  TaskID,
  AgentID,
  CommandRunID,
  ArtifactID,
  UUID,
  ISOTimeString,
} from '@aircoding/contracts'

import { DatabaseHandle } from '../MigrationRunner.js'
import { assertEnumValues } from '../assertEnum.js'

// =============================================================================
// Types - per db-schema §14
// =============================================================================

export type DiagnosticSeverity = 'error' | 'warning' | 'info' | 'hint'

export interface DiagnosticRecord {
  id: UUID
  session_id: SessionID
  task_id?: TaskID
  agent_id?: AgentID
  command_run_id?: CommandRunID
  artifact_id?: ArtifactID
  language?: string
  toolchain?: string
  severity: DiagnosticSeverity
  file?: string
  line?: number
  column?: number
  code?: string
  message: string
  semantic_signature: string
  created_at: ISOTimeString
  metadata_json?: string
}

export type DiagnosticInsert = Omit<DiagnosticRecord, 'id'> & {
  id?: UUID
}

export type DiagnosticUpdate = Partial<Omit<DiagnosticRecord, 'id' | 'session_id' | 'created_at' | 'semantic_signature'>>

// =============================================================================
// DiagnosticRepository
// =============================================================================

export class DiagnosticRepository implements Repository<DiagnosticRecord, DiagnosticInsert, DiagnosticUpdate> {
  private db: DatabaseHandle

  constructor(db: DatabaseHandle) {
    this.db = db
  }

  /**
   * Get a diagnostic by ID.
   */
  async get(id: UUID, tx?: TransactionHandle): Promise<DiagnosticRecord | undefined> {
    const stmt = (tx?.db ?? this.db).prepare('SELECT * FROM diagnostics WHERE id = ?')
    const row = stmt.get(id) as DiagnosticRecord | undefined
    return row
  }

  /**
   * Insert a new diagnostic.
   */
  async insert(record: DiagnosticInsert, tx?: TransactionHandle): Promise<void> {
    // Validate enum columns
    assertEnumValues('diagnostics', {
      severity: record.severity,
    })

    const stmt = (tx?.db ?? this.db).prepare(`
      INSERT INTO diagnostics (
        id, session_id, task_id, agent_id, command_run_id, artifact_id,
        language, toolchain, severity,
        file, line, column, code,
        message, semantic_signature,
        created_at, metadata_json
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `)

    stmt.run(
      record.id,
      record.session_id,
      record.task_id ?? null,
      record.agent_id ?? null,
      record.command_run_id ?? null,
      record.artifact_id ?? null,
      record.language ?? null,
      record.toolchain ?? null,
      record.severity,
      record.file ?? null,
      record.line ?? null,
      record.column ?? null,
      record.code ?? null,
      record.message,
      record.semantic_signature,
      record.created_at,
      record.metadata_json ?? null,
    )
  }

  /**
   * Update an existing diagnostic.
   */
  async update(id: UUID, patch: DiagnosticUpdate, tx?: TransactionHandle): Promise<void> {
    // Validate enum columns if present
    if (patch.severity !== undefined) {
      assertEnumValues('diagnostics', { severity: patch.severity })
    }

    const fields: string[] = []
    const values: unknown[] = []

    if (patch.task_id !== undefined) {
      fields.push('task_id = ?')
      values.push(patch.task_id)
    }
    if (patch.agent_id !== undefined) {
      fields.push('agent_id = ?')
      values.push(patch.agent_id)
    }
    if (patch.command_run_id !== undefined) {
      fields.push('command_run_id = ?')
      values.push(patch.command_run_id)
    }
    if (patch.artifact_id !== undefined) {
      fields.push('artifact_id = ?')
      values.push(patch.artifact_id)
    }
    if (patch.language !== undefined) {
      fields.push('language = ?')
      values.push(patch.language)
    }
    if (patch.toolchain !== undefined) {
      fields.push('toolchain = ?')
      values.push(patch.toolchain)
    }
    if (patch.severity !== undefined) {
      fields.push('severity = ?')
      values.push(patch.severity)
    }
    if (patch.file !== undefined) {
      fields.push('file = ?')
      values.push(patch.file)
    }
    if (patch.line !== undefined) {
      fields.push('line = ?')
      values.push(patch.line)
    }
    if (patch.column !== undefined) {
      fields.push('column = ?')
      values.push(patch.column)
    }
    if (patch.code !== undefined) {
      fields.push('code = ?')
      values.push(patch.code)
    }
    if (patch.message !== undefined) {
      fields.push('message = ?')
      values.push(patch.message)
    }
    if (patch.metadata_json !== undefined) {
      fields.push('metadata_json = ?')
      values.push(patch.metadata_json)
    }

    if (fields.length === 0) {
      return // Nothing to update
    }

    values.push(id)
    const stmt = (tx?.db ?? this.db).prepare(`UPDATE diagnostics SET ${fields.join(', ')} WHERE id = ?`)
    stmt.run(...values)
  }

  // =============================================================================
  // Extra methods
  // =============================================================================

  /**
   * List diagnostics by semantic signature (exact match).
   */
  async list_by_signature(semantic_signature: string): Promise<DiagnosticRecord[]> {
    const stmt = this.db.prepare(
      'SELECT * FROM diagnostics WHERE semantic_signature = ? ORDER BY created_at DESC',
    )
    return stmt.all(semantic_signature) as DiagnosticRecord[]
  }

  /**
   * List diagnostics for a specific command run.
   */
  async list_by_command_run(command_run_id: CommandRunID): Promise<DiagnosticRecord[]> {
    const stmt = this.db.prepare(
      'SELECT * FROM diagnostics WHERE command_run_id = ? ORDER BY file, line, column',
    )
    return stmt.all(command_run_id) as DiagnosticRecord[]
  }
}