/**
 * DatabaseManager - Storage layer for session databases
 *
 * Implements TransactionManager (contracts §6) over Bun SQLite.
 * Per system-detailed-design.md §4.1 and db-schema-v1.md §1.
 */

import { Database } from 'bun:sqlite'
import type {
  DatabaseHandle,
  TransactionHandle,
  TransactionManager,
} from '@aircoding/contracts'

/**
 * DatabaseManager implements TransactionManager interface
 * for Bun SQLite with WAL mode and proper pragma configuration.
 */
export class DatabaseManager implements TransactionManager {
  private db: Database | null = null
  private path: string | null = null

  /**
   * Opens a database connection and applies required pragmas.
   * Per db-schema §1: journal_mode=WAL, synchronous=NORMAL, foreign_keys=OFF
   */
  open(path: string): DatabaseHandle {
    // Close existing connection if any
    if (this.db) {
      this.db.close()
    }

    // Open new connection with Bun SQLite
    this.db = new Database(path)
    this.path = path

    // Apply required pragmas per db-schema §1
    this.applyPragmas(this.db)

    return { path }
  }

  /**
   * Applies the required SQLite pragmas per db-schema §1.
   */
  private applyPragmas(db: Database): void {
    // WAL mode supports concurrent read/write patterns
    db.exec('PRAGMA journal_mode = WAL')
    // NORMAL is sufficient for local session state and faster than FULL
    db.exec('PRAGMA synchronous = NORMAL')
    // Foreign keys disabled in MVP to reduce migration/recovery complexity
    db.exec('PRAGMA foreign_keys = OFF')
  }

  /**
   * Executes a function within a transaction.
   * Wraps BEGIN/COMMIT/ROLLBACK - nested calls reuse active handle
   * (single-writer per session DB, so no real nesting needed).
   */
  async transaction<T>(fn: (tx: TransactionHandle) => Promise<T>): Promise<T> {
    if (!this.db) {
      throw new Error('Database not opened. Call open() first.')
    }

    // Get or create transaction handle
    const tx = this.handleFor(this.db)

    // Check if we're already in a transaction (nested call)
    const isNested = this.db.inTransaction

    if (!isNested) {
      // Start new transaction
      this.db.exec('BEGIN')
    }

    try {
      // Execute the user function with the transaction handle
      const result = await fn(tx)

      // Commit if we started a new transaction (not nested)
      if (!isNested) {
        this.db.exec('COMMIT')
      }

      return result
    } catch (error) {
      // Rollback if we started a new transaction (not nested)
      if (!isNested) {
        this.db.exec('ROLLBACK')
      }
      throw error
    }
  }

  /**
   * Creates a TransactionHandle for the given database.
   * The id is an opaque token that maps to the active raw transaction.
   * The db property carries the database handle for repository use within
   * the transaction scope.
   */
  private handleFor(db: Database): TransactionHandle {
    // Generate a unique transaction id using current timestamp + random
    const id = `tx_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`
    return { id, db }
  }

  /**
   * Returns the raw database instance for repository use.
   * Only available when a database is open.
   */
  getRawDatabase(): Database | null {
    return this.db
  }

  /**
   * Closes the database connection.
   */
  close(): void {
    if (this.db) {
      this.db.close()
      this.db = null
      this.path = null
    }
  }

  /**
   * Checks if a database is currently open.
   */
  isOpen(): boolean {
    return this.db !== null
  }

  /**
   * Gets the current database path.
   */
  getPath(): string | null {
    return this.path
  }
}

/**
 * Creates a new DatabaseManager instance.
 */
export function createDatabaseManager(): DatabaseManager {
  return new DatabaseManager()
}