/**
 * Database Service — SQLite 数据库管理
 *
 * 使用 better-sqlite3（同步、高性能、主进程专用）。
 * 负责数据库初始化、Schema 迁移、连接管理。
 *
 * @see docs/MetonaAI-Desktop 架构与交互设计.html — 数据库配置
 * @see standard/开发规范.md — 禁止自写数据库层，使用 better-sqlite3
 */

import Database from 'better-sqlite3';
import { join } from 'path';
import { app } from 'electron';
import { existsSync, mkdirSync } from 'fs';
import log from 'electron-log';

/**
 * L-8 修复: 提取 toErrorMessage 工具函数，消除 5 处重复的 error instanceof Error 三元表达式
 */
function toErrorMessage(error: unknown): string {
  return error instanceof Error ? error.message : String(error);
}

export class DatabaseService {
  private db: Database.Database | null = null;
  private dbPath: string;

  constructor(workspacePath?: string) {
    const baseDir = workspacePath ?? join(app.getPath('userData'), 'MetonaWorkspaces', 'default');
    const metonaDir = join(baseDir, '.metona');

    // 确保 .metona 目录存在
    if (!existsSync(metonaDir)) {
      mkdirSync(metonaDir, { recursive: true });
    }

    this.dbPath = join(metonaDir, 'agent.db');
  }

  /**
   * 初始化数据库（创建表结构）
   */
  initialize(): void {
    if (this.db) {
      log.warn('Database already initialized');
      return;
    }

    log.info(`Initializing database: ${this.dbPath}`);
    this.db = new Database(this.dbPath);

    // 启用 WAL 模式（更好的并发性能）
    this.db.pragma('journal_mode = WAL');
    this.db.pragma('foreign_keys = ON');

    this.createTables();
    this.runMigrations();
    this.seedDefaults();

    log.info('Database initialized successfully');
  }

  /**
   * 获取数据库实例
   */
  getDB(): Database.Database {
    if (!this.db) {
      throw new Error('Database not initialized. Call initialize() first.');
    }
    return this.db;
  }

  /**
   * 关闭数据库
   */
  close(): void {
    if (this.db) {
      this.db.close();
      this.db = null;
      log.info('Database closed');
    }
  }

  /**
   * 创建表结构
   */
  private createTables(): void {
    const db = this.db!;

    db.exec(`
      -- ===== 会话表 =====
      CREATE TABLE IF NOT EXISTS sessions (
        id              TEXT PRIMARY KEY,
        title           TEXT NOT NULL DEFAULT '新会话',
        created_at      INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
        updated_at      INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
        message_count   INTEGER NOT NULL DEFAULT 0,
        total_tokens    INTEGER NOT NULL DEFAULT 0,
        pinned          INTEGER NOT NULL DEFAULT 0,
        archived        INTEGER NOT NULL DEFAULT 0,
        metadata        TEXT DEFAULT '{}'
      );

      -- ===== 消息表 =====
      -- C-6 修复: content 允许 NULL — assistant 消息仅有 tool_calls 时 content 必须为 null
      -- @see project_memory.md — Assistant messages with tool_calls must set content to null
      CREATE TABLE IF NOT EXISTS messages (
        id              TEXT PRIMARY KEY,
        session_id      TEXT NOT NULL,
        role            TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'system', 'tool')),
        content         TEXT,
        reasoning_content TEXT,
        tool_calls      TEXT,
        tool_result     TEXT,
        attachments     TEXT,
        iteration       INTEGER,
        created_at      INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
        FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
      );

      -- ===== 配置表 =====
      CREATE TABLE IF NOT EXISTS app_config (
        key             TEXT PRIMARY KEY,
        value           TEXT NOT NULL,
        category        TEXT NOT NULL DEFAULT 'general',
        updated_at      INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
      );

      -- ===== 审计日志表 =====
      CREATE TABLE IF NOT EXISTS audit_logs (
        id              INTEGER PRIMARY KEY AUTOINCREMENT,
        session_id      TEXT,
        iteration       INTEGER,
        event_type      TEXT NOT NULL,
        actor           TEXT NOT NULL DEFAULT 'system',
        target          TEXT NOT NULL,
        details         TEXT,
        outcome         TEXT,
        duration_ms     INTEGER,
        created_at      INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
        prev_hash       TEXT,
        current_hash    TEXT
      );

      -- ===== MCP 服务配置表 =====
      CREATE TABLE IF NOT EXISTS mcp_servers (
        id              TEXT PRIMARY KEY,
        name            TEXT NOT NULL UNIQUE,
        transport       TEXT NOT NULL CHECK(transport IN ('stdio', 'sse')),
        command         TEXT,
        args            TEXT,
        url             TEXT,
        headers         TEXT,
        enabled         INTEGER NOT NULL DEFAULT 1,
        last_connected  INTEGER,
        error_message   TEXT,
        created_at      INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
        updated_at      INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
      );

      -- ===== 情节记忆表 =====
      CREATE TABLE IF NOT EXISTS episodic_memories (
        id              TEXT PRIMARY KEY,
        session_id      TEXT,
        content         TEXT NOT NULL,
        summary         TEXT,
        source          TEXT NOT NULL,
        importance      REAL DEFAULT 0.5,
        created_at      INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
        expires_at      INTEGER
      );

      -- ===== 语义记忆表 =====
      CREATE TABLE IF NOT EXISTS semantic_memories (
        id              TEXT PRIMARY KEY,
        key             TEXT NOT NULL UNIQUE,
        value           TEXT NOT NULL,
        category        TEXT,
        confidence      REAL DEFAULT 0.8,
        source_session  TEXT,
        created_at      INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
        updated_at      INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
        access_count    INTEGER DEFAULT 0
      );

      -- ===== 工作记忆表 =====
      CREATE TABLE IF NOT EXISTS working_memories (
        id              TEXT PRIMARY KEY,
        session_id      TEXT NOT NULL,
        task_id         TEXT NOT NULL,
        key             TEXT NOT NULL,
        value           TEXT NOT NULL,
        updated_at      INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
        UNIQUE(session_id, task_id, key)
      );

      -- ===== v0.2.0: 任务表 =====
      CREATE TABLE IF NOT EXISTS tasks (
        id              TEXT PRIMARY KEY,
        session_id      TEXT NOT NULL,
        title           TEXT NOT NULL,
        description     TEXT NOT NULL DEFAULT '',
        status          TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending', 'in_progress', 'completed', 'blocked', 'cancelled')),
        priority        TEXT NOT NULL DEFAULT 'medium' CHECK(priority IN ('low', 'medium', 'high', 'critical')),
        parent_id       TEXT,
        assigned_to     TEXT,
        order_idx       INTEGER NOT NULL DEFAULT 0,
        created_at      INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
        updated_at      INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
        completed_at    INTEGER,
        FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE,
        FOREIGN KEY (parent_id) REFERENCES tasks(id) ON DELETE CASCADE
      );

      -- ===== 索引 =====
      CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id, created_at);
      CREATE INDEX IF NOT EXISTS idx_messages_role ON messages(role);
      CREATE INDEX IF NOT EXISTS idx_sessions_updated ON sessions(updated_at DESC);
      CREATE INDEX IF NOT EXISTS idx_sessions_pinned ON sessions(pinned DESC, updated_at DESC);
      CREATE INDEX IF NOT EXISTS idx_audit_session ON audit_logs(session_id);
      CREATE INDEX IF NOT EXISTS idx_audit_type ON audit_logs(event_type);
      CREATE INDEX IF NOT EXISTS idx_audit_created ON audit_logs(created_at);
      CREATE INDEX IF NOT EXISTS idx_config_category ON app_config(category);
      CREATE INDEX IF NOT EXISTS idx_semantic_key ON semantic_memories(key);
      CREATE INDEX IF NOT EXISTS idx_semantic_category ON semantic_memories(category);
      CREATE INDEX IF NOT EXISTS idx_episodic_session ON episodic_memories(session_id);
      CREATE INDEX IF NOT EXISTS idx_episodic_importance ON episodic_memories(importance DESC);
      CREATE INDEX IF NOT EXISTS idx_working_session_task ON working_memories(session_id, task_id);
      CREATE INDEX IF NOT EXISTS idx_tasks_session ON tasks(session_id, order_idx);
      CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(session_id, status);
      CREATE INDEX IF NOT EXISTS idx_tasks_parent ON tasks(parent_id);
    `);

    // 审计日志防篡改触发器（INSERT-ONLY）
    db.exec(`
      CREATE TRIGGER IF NOT EXISTS audit_no_update BEFORE UPDATE ON audit_logs
      BEGIN
        SELECT RAISE(ABORT, 'Audit logs are INSERT-ONLY. Modification is not allowed.');
      END;
    `);

    db.exec(`
      CREATE TRIGGER IF NOT EXISTS audit_no_delete BEFORE DELETE ON audit_logs
      BEGIN
        SELECT RAISE(ABORT, 'Audit logs are INSERT-ONLY. Deletion is not allowed.');
      END;
    `);

    log.info('Database tables created');
  }

  /**
   * 运行数据库迁移
   */
  private runMigrations(): void {
    const db = this.db!;

    // L-6 修复: 提取 tryAddColumn 辅助方法，消除 4 处重复的 try/catch 模式
    // L-8 修复: 使用 toErrorMessage 替代重复的 error instanceof Error 三元表达式
    const tryAddColumn = (table: string, column: string, type: string, migrationName: string) => {
      try {
        db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${type}`);
        log.info(`[DB] Migration: added ${column} column to ${table}`);
      } catch (error) {
        // 只忽略 "duplicate column" 错误（列已存在），其他错误必须抛出
        const msg = toErrorMessage(error);
        if (!msg.includes('duplicate column')) {
          throw error;
        }
      }
    };

    // #33 修复: 整个迁移批次包裹在事务中，保证原子性
    //   若某个 migration 部分失败（如 ALTER TABLE 成功，CREATE INDEX 失败），
    //   事务回滚，数据库不会处于部分变更的不一致状态，下次启动可安全重试。
    //   better-sqlite3 的事务是同步原子的，嵌套事务使用 SAVEPOINT 实现。
    const runAllMigrations = db.transaction(() => {
      // 迁移 1: messages 表添加 attachments 列
      tryAddColumn('messages', 'attachments', 'TEXT', 'attachments');
      // 迁移 2: audit_logs 表添加 iteration 列
      tryAddColumn('audit_logs', 'iteration', 'INTEGER', 'iteration');
      // v0.2.0 迁移 3: audit_logs 表添加 prev_hash 列（链式哈希）
      tryAddColumn('audit_logs', 'prev_hash', 'TEXT', 'prev_hash');
      // v0.2.0 迁移 4: audit_logs 表添加 current_hash 列（链式哈希）
      tryAddColumn('audit_logs', 'current_hash', 'TEXT', 'current_hash');

      // C-6 修复 迁移 5: 重建 messages 表，将 content 列从 NOT NULL 改为允许 NULL
      // @see project_memory.md — Assistant messages with tool_calls must set content to null
      // SQLite 不支持 ALTER COLUMN，需要重建表
      try {
        // 检测 content 列是否有 NOT NULL 约束
        const columns = db.prepare('PRAGMA table_info(messages)').all() as Array<{ name: string; notnull: number }>;
        const contentCol = columns.find((c) => c.name === 'content');
        if (contentCol && contentCol.notnull === 1) {
          log.info('[DB] Migration: rebuilding messages table to allow NULL content');

          // #33 修复: 重建表的多步骤包裹在嵌套事务中，部分失败时回滚
          //   避免 CREATE messages_new 成功但 DROP/RENAME 失败导致数据丢失或 schema 不一致
          const rebuildMessages = db.transaction(() => {
            db.exec(`
              CREATE TABLE IF NOT EXISTS messages_new (
                id              TEXT PRIMARY KEY,
                session_id      TEXT NOT NULL,
                role            TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'system', 'tool')),
                content         TEXT,
                reasoning_content TEXT,
                tool_calls      TEXT,
                tool_result     TEXT,
                attachments     TEXT,
                iteration       INTEGER,
                created_at      INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
                FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
              );

              INSERT INTO messages_new (id, session_id, role, content, reasoning_content, tool_calls, tool_result, attachments, iteration, created_at)
              SELECT id, session_id, role, content, reasoning_content, tool_calls, tool_result, attachments, iteration, created_at
              FROM messages;

              DROP TABLE messages;
              ALTER TABLE messages_new RENAME TO messages;
            `);

            // 重建索引
            // #42 确认: idx_messages_session 已是 (session_id, created_at) 复合索引，
            //   覆盖 getMessages 的 WHERE session_id = ? ORDER BY created_at ASC 查询，
            //   工单描述"仅有 session_id 单字段索引"不准确，无需额外添加 idx_messages_session_timestamp
            db.exec(`
              CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id, created_at);
              CREATE INDEX IF NOT EXISTS idx_messages_role ON messages(role);
            `);
          });
          rebuildMessages();

          log.info('[DB] Migration: messages table rebuilt successfully (content now allows NULL)');
        }
      } catch (error) {
        // L-8 修复: 使用 toErrorMessage 替代重复的三元表达式
        const msg = toErrorMessage(error);
        log.warn(`[DB] Migration 5 (messages content NULL) skipped: ${msg}`);
        // 非致命错误 — 如果迁移失败，NOT NULL 约束仍生效，saveMessage 会保存空字符串
      }
    });

    runAllMigrations();
  }

  /**
   * 插入默认配置
   */
  private seedDefaults(): void {
    const db = this.db!;

    const defaults: Array<{ key: string; value: string; category: string }> = [
      // LLM 配置（无硬编码值，用户必须手动配置）
      { key: 'llm.provider', value: '""', category: 'llm' },
      { key: 'llm.model', value: '""', category: 'llm' },
      { key: 'llm.apiKey', value: '""', category: 'llm' },
      { key: 'llm.baseURL', value: '""', category: 'llm' },
      { key: 'llm.temperature', value: '0', category: 'llm' },
      { key: 'llm.maxTokens', value: '63488', category: 'llm' },
      { key: 'llm.fallbackProvider', value: '""', category: 'llm' },
      { key: 'llm.fallbackModel', value: '""', category: 'llm' },

      // Agent 配置
      { key: 'agent.maxIterations', value: '20', category: 'agent' },
      { key: 'agent.totalTimeoutMs', value: '600000', category: 'agent' },
      { key: 'agent.enableThinking', value: 'true', category: 'agent' },
      { key: 'agent.thinkingEffort', value: '"high"', category: 'agent' },
      { key: 'agent.enableReflection', value: 'false', category: 'agent' },
      // C-10 修复: 补充缺失的 agent 配置默认值
      // @see project_memory.md — Tool confirmation timeout is configurable via agent.confirmationTimeoutMs (30s~600s, default 120s)
      { key: 'agent.confirmationTimeoutMs', value: '120000', category: 'agent' },
      { key: 'agent.toolExecutionTimeoutMs', value: '120000', category: 'agent' },

      // 安全配置
      { key: 'security.requireWriteConfirmation', value: 'true', category: 'security' },
      { key: 'security.maxFileWriteSizeKB', value: '1024', category: 'security' },
      { key: 'security.promptInjectionDefense', value: 'true', category: 'security' },

      // UI 配置
      { key: 'ui.theme', value: '"auto"', category: 'ui' },
      { key: 'ui.animationMode', value: '"auto"', category: 'ui' },
      { key: 'ui.fontSize', value: '"medium"', category: 'ui' },

      // 日志配置
      { key: 'logging.level', value: '"info"', category: 'logging' },
      { key: 'logging.auditEnabled', value: 'true', category: 'logging' },
      { key: 'logging.traceEnabled', value: 'true', category: 'logging' },

      // Ollama 配置
      { key: 'ollama.numCtx', value: 'null', category: 'ollama' },

      // Provider 上下文窗口配置（用于 Engine 压缩判断和 UI 显示）
      // v0.3.1: DeepSeek/Agnes 改为可配置，不再写死 1M
      { key: 'deepseek.contextWindow', value: '1000000', category: 'deepseek' },
      { key: 'agnes.contextWindow', value: '1000000', category: 'agnes' },
      // v0.3.4: MiMo 上下文窗口（与 DeepSeek 一致，1M）
      { key: 'mimo.contextWindow', value: '1000000', category: 'mimo' },

      // Onboarding
      { key: 'onboarding.completed', value: 'false', category: 'general' },
    ];

    const insert = db.prepare(`
      INSERT OR IGNORE INTO app_config (key, value, category) VALUES (?, ?, ?)
    `);

    const insertMany = db.transaction((items: typeof defaults) => {
      for (const item of items) {
        insert.run(item.key, item.value, item.category);
      }
    });

    insertMany(defaults);
    log.info('Default config seeded');
  }
}
