/**
 * WorkspaceRepository - CRUD + list_by_status, list_gc_candidates for workspaces table (§16)
 *
 * Implements Repository<WorkspaceRecord, WorkspaceInsert, WorkspaceUpdate> per contracts §6.
 *
 * @module packages/runtime/src/storage/repositories/WorkspaceRepository
 */

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

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

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

export type WorkspaceStrategy = 'main' | 'worktree' | 'isolated_copy'
export type WorkspaceStatus = 'active' | 'merged' | 'conflicted' | 'abandoned' | 'cleaned'

export interface WorkspaceRecord {
  id: WorkspaceID
  session_id: SessionID
  task_id?: TaskID
  agent_id?: AgentID
  path: string
  strategy: WorkspaceStrategy
  status: WorkspaceStatus
  base_ref?: string
  branch_name?: string
  created_at: ISOTimeString
  merged_at?: ISOTimeString
  metadata_json?: string
}

export type WorkspaceInsert = Omit<WorkspaceRecord, 'id'> & {
  id?: WorkspaceID
}

export type WorkspaceUpdate = Partial<Omit<WorkspaceRecord, 'id' | 'session_id' | 'created_at'>>

// =============================================================================
// WorkspaceRepository
// =============================================================================

export class WorkspaceRepository implements Repository<WorkspaceRecord, WorkspaceInsert, WorkspaceUpdate> {
  private db: DatabaseHandle

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

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

  /**
   * Insert a new workspace.
   */
  async insert(record: WorkspaceInsert, tx?: TransactionHandle): Promise<void> {
    // Validate enum columns
    assertEnumValues('workspaces', {
      strategy: record.strategy,
      status: record.status,
    })

    const stmt = (tx?.db ?? this.db).prepare(`
      INSERT INTO workspaces (
        id, session_id, task_id, agent_id,
        path, strategy, status,
        base_ref, branch_name,
        created_at, merged_at, metadata_json
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `)

    stmt.run(
      record.id,
      record.session_id,
      record.task_id ?? null,
      record.agent_id ?? null,
      record.path,
      record.strategy,
      record.status,
      record.base_ref ?? null,
      record.branch_name ?? null,
      record.created_at,
      record.merged_at ?? null,
      record.metadata_json ?? null,
    )
  }

  /**
   * Update an existing workspace.
   */
  async update(id: WorkspaceID, patch: WorkspaceUpdate, tx?: TransactionHandle): Promise<void> {
    // Validate enum columns if present
    if (patch.strategy !== undefined) {
      assertEnumValues('workspaces', { strategy: patch.strategy })
    }
    if (patch.status !== undefined) {
      assertEnumValues('workspaces', { status: patch.status })
    }

    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.path !== undefined) {
      fields.push('path = ?')
      values.push(patch.path)
    }
    if (patch.status !== undefined) {
      fields.push('status = ?')
      values.push(patch.status)
    }
    if (patch.base_ref !== undefined) {
      fields.push('base_ref = ?')
      values.push(patch.base_ref)
    }
    if (patch.branch_name !== undefined) {
      fields.push('branch_name = ?')
      values.push(patch.branch_name)
    }
    if (patch.merged_at !== undefined) {
      fields.push('merged_at = ?')
      values.push(patch.merged_at)
    }
    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 workspaces SET ${fields.join(', ')} WHERE id = ?`)
    stmt.run(...values)
  }

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

  /**
   * List workspaces by status for a session.
   */
  async list_by_status(session_id: SessionID, statuses: WorkspaceStatus[]): Promise<WorkspaceRecord[]> {
    if (statuses.length === 0) {
      const stmt = this.db.prepare(
        'SELECT * FROM workspaces WHERE session_id = ? ORDER BY created_at DESC',
      )
      return stmt.all(session_id) as WorkspaceRecord[]
    }

    const placeholders = statuses.map(() => '?').join(', ')
    const stmt = this.db.prepare(
      `SELECT * FROM workspaces WHERE session_id = ? AND status IN (${placeholders}) ORDER BY created_at DESC`,
    )
    return stmt.all(session_id, ...statuses) as WorkspaceRecord[]
  }

  /**
   * List garbage collection candidates - workspaces that are not active
   * and have been merged or abandoned for longer than the retention period.
   *
   * @param session_id - The session to query
   * @param older_than_hours - Only include workspaces older than this many hours (default: 24)
   */
  async list_gc_candidates(session_id: SessionID, older_than_hours: number = 24): Promise<WorkspaceRecord[]> {
    const cutoff = new Date(Date.now() - older_than_hours * 60 * 60 * 1000).toISOString()

    const stmt = this.db.prepare(`
      SELECT * FROM workspaces
      WHERE session_id = ?
        AND status IN ('merged', 'conflicted', 'abandoned', 'cleaned')
        AND (merged_at IS NOT NULL AND merged_at < ?)
        OR (merged_at IS NULL AND created_at < ?)
      ORDER BY created_at ASC
    `)
    return stmt.all(session_id, cutoff, cutoff) as WorkspaceRecord[]
  }
}