use once_cell::sync::OnceCell;
use r2d2::Pool;
use r2d2_sqlite::SqliteConnectionManager;
use std::path::PathBuf;

pub type DbPool = Pool<SqliteConnectionManager>;

static POOL: OnceCell<DbPool> = OnceCell::new();

pub fn init(data_dir: PathBuf) {
    std::fs::create_dir_all(&data_dir).expect("create data dir");
    let db_path = data_dir.join("dev-manager.db");
    let manager = SqliteConnectionManager::file(db_path);
    let pool = Pool::new(manager).expect("create sqlite pool");

    {
        let conn = pool.get().expect("get conn for migration");
        conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;")
            .expect("pragma");
        migrate(&conn);
    }

    POOL.set(pool).expect("pool already initialized");
}

pub fn pool() -> &'static DbPool {
    POOL.get().expect("db not initialized")
}

/// pool 未初始化时返回 None，测试环境中安全调用。
pub(crate) fn try_pool() -> Option<&'static DbPool> {
    POOL.get()
}

fn migrate(conn: &rusqlite::Connection) {
    conn.execute_batch(
        "
        CREATE TABLE IF NOT EXISTS projects (
            id               TEXT PRIMARY KEY,
            name             TEXT NOT NULL,
            description      TEXT,
            type             TEXT,
            status           TEXT DEFAULT 'active',
            priority         TEXT DEFAULT 'mid',
            wsl_path         TEXT,
            win_path         TEXT,
            tech_stack       TEXT,
            recent_focus     TEXT,
            startup_commands TEXT,
            updated_at       TEXT DEFAULT (datetime('now'))
        );

        CREATE TABLE IF NOT EXISTS product_groups (
            id          TEXT PRIMARY KEY,
            name        TEXT NOT NULL,
            description TEXT
        );

        CREATE TABLE IF NOT EXISTS project_group_map (
            project_id  TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
            group_id    TEXT NOT NULL REFERENCES product_groups(id) ON DELETE CASCADE,
            role        TEXT,
            PRIMARY KEY (project_id, group_id)
        );

        CREATE TABLE IF NOT EXISTS relations (
            id            INTEGER PRIMARY KEY AUTOINCREMENT,
            from_project  TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
            to_project    TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
            relation_type TEXT,
            note          TEXT
        );

        CREATE TABLE IF NOT EXISTS activity_log (
            id         INTEGER PRIMARY KEY AUTOINCREMENT,
            project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
            summary    TEXT NOT NULL,
            created_at TEXT DEFAULT (datetime('now'))
        );

        CREATE TABLE IF NOT EXISTS settings (
            key   TEXT PRIMARY KEY,
            value TEXT NOT NULL
        );

        CREATE TABLE IF NOT EXISTS project_tools (
            id         TEXT PRIMARY KEY,
            project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
            name       TEXT NOT NULL,
            icon       TEXT NOT NULL DEFAULT '🔧',
            exe_path   TEXT NOT NULL,
            args       TEXT,
            sort_order INTEGER DEFAULT 0
        );

        CREATE TABLE IF NOT EXISTS dev_tools (
            id         TEXT PRIMARY KEY,
            name       TEXT NOT NULL,
            category   TEXT NOT NULL,
            cmd        TEXT,
            path       TEXT,
            version    TEXT,
            note       TEXT,
            created_at TEXT DEFAULT (datetime('now'))
        );

        CREATE TABLE IF NOT EXISTS project_env_tools (
            id          TEXT PRIMARY KEY,
            project_id  TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
            layer       INTEGER NOT NULL,
            name        TEXT NOT NULL,
            source      TEXT,
            version     TEXT,
            note        TEXT,
            updated_at  TEXT DEFAULT (datetime('now'))
        );

        CREATE TABLE IF NOT EXISTS network_monitored_tools (
            id       TEXT PRIMARY KEY,
            name     TEXT NOT NULL,
            exe_path TEXT NOT NULL,
            enabled  INTEGER NOT NULL DEFAULT 1
        );

        CREATE TABLE IF NOT EXISTS network_mode_log (
            id         INTEGER PRIMARY KEY AUTOINCREMENT,
            mode       TEXT NOT NULL,
            created_at TEXT DEFAULT (datetime('now'))
        );

        INSERT OR IGNORE INTO settings (key, value) VALUES ('editor', 'cursor');
        INSERT OR IGNORE INTO settings (key, value) VALUES ('wsl_distro', 'Ubuntu');
        INSERT OR IGNORE INTO settings (key, value) VALUES ('dev_mode', '0');
        INSERT OR IGNORE INTO settings (key, value) VALUES ('github_client_id', '');
        INSERT OR IGNORE INTO settings (key, value) VALUES ('github_client_secret', '');
        INSERT OR IGNORE INTO settings (key, value) VALUES ('mcp_port', '27190');
        ",
    )
    .expect("migrate");

    // 增量迁移：为 dev_tools 添加 env 字段（已存在则忽略）
    let _ = conn.execute(
        "ALTER TABLE dev_tools ADD COLUMN env TEXT NOT NULL DEFAULT 'windows'",
        [],
    );

    // 增量迁移：Git 多空间功能所需新表
    conn.execute_batch(
        "
        CREATE TABLE IF NOT EXISTS git_accounts (
            id           TEXT PRIMARY KEY,
            username     TEXT NOT NULL UNIQUE,
            avatar_url   TEXT,
            provider     TEXT NOT NULL DEFAULT 'github',
            created_at   TEXT DEFAULT (datetime('now'))
        );

        CREATE TABLE IF NOT EXISTS spaces (
            id           TEXT PRIMARY KEY,
            name         TEXT NOT NULL,
            local_root   TEXT NOT NULL,
            is_current   INTEGER NOT NULL DEFAULT 0,
            created_at   TEXT DEFAULT (datetime('now'))
        );

        CREATE TABLE IF NOT EXISTS deploy_commands (
            id         TEXT PRIMARY KEY,
            project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
            name       TEXT NOT NULL,
            command    TEXT NOT NULL,
            run_in     TEXT NOT NULL DEFAULT 'windows'
        );
        ",
    )
    .expect("migrate git spaces");

    // 增量迁移：git_accounts 补 UNIQUE 索引（已存在则忽略）
    let _ = conn.execute(
        "CREATE UNIQUE INDEX IF NOT EXISTS idx_git_accounts_username ON git_accounts(username)",
        [],
    );

    // 增量迁移：projects 表新增 Git 相关字段（已存在则忽略）
    let _ = conn.execute("ALTER TABLE projects ADD COLUMN space_id TEXT", []);
    let _ = conn.execute("ALTER TABLE projects ADD COLUMN repo_url TEXT", []);
    let _ = conn.execute("ALTER TABLE projects ADD COLUMN upstream_url TEXT", []);
    let _ = conn.execute("ALTER TABLE projects ADD COLUMN default_branch TEXT", []);
    let _ = conn.execute("ALTER TABLE projects ADD COLUMN last_push_at TEXT", []);

    // 增量迁移：platform 字段（"windows" | "wsl"，已存在则忽略）
    let _ = conn.execute("ALTER TABLE projects ADD COLUMN platform TEXT", []);

    // 增量迁移：product_groups 表新增 space_id（已存在则忽略）
    let _ = conn.execute("ALTER TABLE product_groups ADD COLUMN space_id TEXT", []);

    // 增量迁移：dev_tools 表新增 space_id（已存在则忽略）
    let _ = conn.execute("ALTER TABLE dev_tools ADD COLUMN space_id TEXT", []);

    // 增量迁移：network_monitored_tools 新增 space_id（已存在则忽略）
    let _ = conn.execute("ALTER TABLE network_monitored_tools ADD COLUMN space_id TEXT", []);

    // 增量迁移：空间级别的 settings 覆盖表
    let _ = conn.execute_batch(
        "CREATE TABLE IF NOT EXISTS space_settings (
            space_id TEXT NOT NULL,
            key      TEXT NOT NULL,
            value    TEXT NOT NULL,
            PRIMARY KEY (space_id, key)
        );"
    );

    // 增量迁移：项目生命周期字段
    let _ = conn.execute("ALTER TABLE projects ADD COLUMN staging_url TEXT", []);
    let _ = conn.execute("ALTER TABLE projects ADD COLUMN prod_url TEXT", []);
    let _ = conn.execute("ALTER TABLE projects ADD COLUMN server_note TEXT", []);

    // ── 方案A重构：project_profiles + project_workspaces ──────────────────────
    conn.execute_batch("
        CREATE TABLE IF NOT EXISTS project_profiles (
            id              TEXT PRIMARY KEY,
            name            TEXT NOT NULL,
            description     TEXT,
            type            TEXT,
            status          TEXT DEFAULT 'active',
            priority        TEXT DEFAULT 'mid',
            tech_stack      TEXT,
            repo_url        TEXT,
            upstream_url    TEXT,
            default_branch  TEXT,
            staging_url     TEXT,
            prod_url        TEXT,
            server_note     TEXT,
            updated_at      TEXT DEFAULT (datetime('now'))
        );
        CREATE TABLE IF NOT EXISTS project_workspaces (
            id               TEXT PRIMARY KEY,
            profile_id       TEXT NOT NULL REFERENCES project_profiles(id) ON DELETE CASCADE,
            space_id         TEXT,
            wsl_path         TEXT,
            win_path         TEXT,
            platform         TEXT DEFAULT 'windows',
            recent_focus     TEXT,
            startup_commands TEXT,
            last_push_at     TEXT,
            updated_at       TEXT DEFAULT (datetime('now'))
        );
        CREATE UNIQUE INDEX IF NOT EXISTS idx_pw_profile_space
            ON project_workspaces(profile_id, ifnull(space_id, ''));
    ").expect("migrate project profiles/workspaces");

    // ── git_repos：GitHub 仓库注册表（全局锚点）────────────────────────────────
    conn.execute_batch("
        CREATE TABLE IF NOT EXISTS git_repos (
            id              TEXT PRIMARY KEY,
            repo_url        TEXT NOT NULL,
            name            TEXT NOT NULL,
            description     TEXT,
            default_branch  TEXT DEFAULT 'main',
            upstream_url    TEXT,
            staging_url     TEXT,
            prod_url        TEXT,
            server_note     TEXT,
            status          TEXT DEFAULT 'active',
            priority        TEXT DEFAULT 'mid',
            tech_stack      TEXT,
            last_synced_at  TEXT,
            created_at      TEXT DEFAULT (datetime('now'))
        );
        CREATE UNIQUE INDEX IF NOT EXISTS idx_git_repos_url ON git_repos(repo_url);
    ").expect("migrate git_repos");

    // project_workspaces 添加 repo_id 外键（已存在则忽略）
    let _ = conn.execute(
        "ALTER TABLE project_workspaces ADD COLUMN repo_id TEXT REFERENCES git_repos(id) ON DELETE SET NULL",
        [],
    );

    // 增量迁移：项目模板系统
    conn.execute_batch("
        CREATE TABLE IF NOT EXISTS project_templates (
            id            TEXT PRIMARY KEY,
            name          TEXT NOT NULL,
            description   TEXT,
            platform      TEXT NOT NULL DEFAULT 'both',
            tech_stack    TEXT,
            init_commands TEXT,
            is_builtin    INTEGER NOT NULL DEFAULT 0,
            created_at    TEXT DEFAULT (datetime('now'))
        );
        CREATE TABLE IF NOT EXISTS project_template_files (
            id          TEXT PRIMARY KEY,
            template_id TEXT NOT NULL REFERENCES project_templates(id) ON DELETE CASCADE,
            path        TEXT NOT NULL,
            content     TEXT NOT NULL DEFAULT '',
            UNIQUE(template_id, path)
        );
    ").expect("migrate project templates");

    // 迁移 project_group_map：将外键从 projects(id) 改为 project_workspaces(id)
    let pgm_sql: Option<String> = conn.query_row(
        "SELECT sql FROM sqlite_master WHERE type='table' AND name='project_group_map'",
        [],
        |row| row.get(0),
    ).ok();
    if pgm_sql.map(|s| s.contains("REFERENCES projects")).unwrap_or(false) {
        conn.execute_batch("
            PRAGMA foreign_keys=OFF;
            CREATE TABLE project_group_map_new (
                project_id TEXT NOT NULL REFERENCES project_workspaces(id) ON DELETE CASCADE,
                group_id   TEXT NOT NULL REFERENCES product_groups(id) ON DELETE CASCADE,
                role       TEXT,
                PRIMARY KEY (project_id, group_id)
            );
            INSERT OR IGNORE INTO project_group_map_new (project_id, group_id, role)
                SELECT m.project_id, m.group_id, m.role
                FROM project_group_map m
                INNER JOIN project_workspaces pw ON pw.id = m.project_id;
            DROP TABLE project_group_map;
            ALTER TABLE project_group_map_new RENAME TO project_group_map;
            PRAGMA foreign_keys=ON;
        ").expect("migrate project_group_map FK to project_workspaces");
    }

    // 迁移 project_tools / activity_log / project_env_tools：
    // 将外键从 projects(id) 改为 project_workspaces(id)
    let pt_sql: Option<String> = conn.query_row(
        "SELECT sql FROM sqlite_master WHERE type='table' AND name='project_tools'",
        [], |row| row.get(0),
    ).ok();
    if pt_sql.as_deref().map(|s| s.contains("REFERENCES projects")).unwrap_or(false) {
        conn.execute_batch("
            PRAGMA foreign_keys=OFF;
            CREATE TABLE project_tools_new (
                id         TEXT PRIMARY KEY,
                project_id TEXT NOT NULL REFERENCES project_workspaces(id) ON DELETE CASCADE,
                name       TEXT NOT NULL,
                icon       TEXT NOT NULL DEFAULT '🔧',
                exe_path   TEXT NOT NULL,
                args       TEXT,
                sort_order INTEGER DEFAULT 0
            );
            INSERT OR IGNORE INTO project_tools_new
                SELECT t.id, t.project_id, t.name, t.icon, t.exe_path, t.args, t.sort_order
                FROM project_tools t
                INNER JOIN project_workspaces pw ON pw.id = t.project_id;
            DROP TABLE project_tools;
            ALTER TABLE project_tools_new RENAME TO project_tools;
            PRAGMA foreign_keys=ON;
        ").expect("migrate project_tools FK to project_workspaces");
    }

    let al_sql: Option<String> = conn.query_row(
        "SELECT sql FROM sqlite_master WHERE type='table' AND name='activity_log'",
        [], |row| row.get(0),
    ).ok();
    if al_sql.as_deref().map(|s| s.contains("REFERENCES projects")).unwrap_or(false) {
        conn.execute_batch("
            PRAGMA foreign_keys=OFF;
            CREATE TABLE activity_log_new (
                id         INTEGER PRIMARY KEY AUTOINCREMENT,
                project_id TEXT NOT NULL REFERENCES project_workspaces(id) ON DELETE CASCADE,
                summary    TEXT NOT NULL,
                created_at TEXT DEFAULT (datetime('now'))
            );
            INSERT OR IGNORE INTO activity_log_new (id, project_id, summary, created_at)
                SELECT a.id, a.project_id, a.summary, a.created_at
                FROM activity_log a
                INNER JOIN project_workspaces pw ON pw.id = a.project_id;
            DROP TABLE activity_log;
            ALTER TABLE activity_log_new RENAME TO activity_log;
            PRAGMA foreign_keys=ON;
        ").expect("migrate activity_log FK to project_workspaces");
    }

    let pet_sql: Option<String> = conn.query_row(
        "SELECT sql FROM sqlite_master WHERE type='table' AND name='project_env_tools'",
        [], |row| row.get(0),
    ).ok();
    if pet_sql.as_deref().map(|s| s.contains("REFERENCES projects")).unwrap_or(false) {
        conn.execute_batch("
            PRAGMA foreign_keys=OFF;
            CREATE TABLE project_env_tools_new (
                id          TEXT PRIMARY KEY,
                project_id  TEXT NOT NULL REFERENCES project_workspaces(id) ON DELETE CASCADE,
                layer       INTEGER NOT NULL,
                name        TEXT NOT NULL,
                source      TEXT,
                version     TEXT,
                note        TEXT,
                updated_at  TEXT DEFAULT (datetime('now'))
            );
            INSERT OR IGNORE INTO project_env_tools_new
                SELECT t.id, t.project_id, t.layer, t.name, t.source, t.version, t.note, t.updated_at
                FROM project_env_tools t
                INNER JOIN project_workspaces pw ON pw.id = t.project_id;
            DROP TABLE project_env_tools;
            ALTER TABLE project_env_tools_new RENAME TO project_env_tools;
            PRAGMA foreign_keys=ON;
        ").expect("migrate project_env_tools FK to project_workspaces");
    }

    // 从旧 projects 表迁移数据（INSERT OR IGNORE 保证幂等）
    let _ = conn.execute_batch("
        INSERT OR IGNORE INTO project_profiles
            (id, name, description, type, status, priority, tech_stack,
             repo_url, upstream_url, default_branch,
             staging_url, prod_url, server_note, updated_at)
        SELECT
            'prof-' || id,
            name, description, type, status, priority, tech_stack,
            repo_url, upstream_url, default_branch,
            staging_url, prod_url, server_note, updated_at
        FROM projects;

        INSERT OR IGNORE INTO project_workspaces
            (id, profile_id, space_id, wsl_path, win_path, platform,
             recent_focus, startup_commands, last_push_at, updated_at)
        SELECT
            id,
            'prof-' || id,
            space_id, wsl_path, win_path, COALESCE(platform, 'windows'),
            recent_focus, startup_commands, last_push_at, updated_at
        FROM projects;
    ");

    // 增量迁移：project_profiles 加 tags 字段
    let _ = conn.execute("ALTER TABLE project_profiles ADD COLUMN tags TEXT", []);

    // 标签管理表
    conn.execute_batch("
        CREATE TABLE IF NOT EXISTS project_tags (
            id    TEXT PRIMARY KEY,
            name  TEXT NOT NULL,
            color TEXT NOT NULL DEFAULT '#8b5cf6'
        );
        CREATE TABLE IF NOT EXISTS project_tag_map (
            profile_id TEXT NOT NULL,
            tag_id     TEXT NOT NULL,
            PRIMARY KEY (profile_id, tag_id)
        );
    ").expect("create tag tables");

    // 增量迁移：product_groups 加 sort_order 字段
    let _ = conn.execute(
        "ALTER TABLE product_groups ADD COLUMN sort_order INTEGER NOT NULL DEFAULT 0",
        [],
    );

    // CI/CD 配置表（替代 deploy_commands）
    conn.execute_batch("
        CREATE TABLE IF NOT EXISTS cicd_configs (
            id              TEXT PRIMARY KEY,
            project_id      TEXT NOT NULL UNIQUE,
            config_type     TEXT NOT NULL DEFAULT 'web_ssh',
            trigger_branch  TEXT NOT NULL DEFAULT 'main',
            build_command   TEXT DEFAULT 'pnpm build',
            node_version    TEXT DEFAULT '20',
            server_host     TEXT,
            server_user     TEXT DEFAULT 'ubuntu',
            deploy_path     TEXT,
            start_command   TEXT DEFAULT 'pm2 restart app',
            oss_endpoint    TEXT DEFAULT 'oss-cn-hangzhou.aliyuncs.com',
            created_at      TEXT DEFAULT (datetime('now'))
        );
    ").expect("create cicd_configs table");

    // 资源视角画布状态（每个产品组独立保存）
    conn.execute_batch("
        CREATE TABLE IF NOT EXISTS canvas_states (
            group_id    TEXT PRIMARY KEY,
            data        TEXT NOT NULL,
            updated_at  TEXT DEFAULT (datetime('now'))
        );
    ").expect("create canvas_states table");

    // 运行时可观测性日志（最多保留 500 条）
    conn.execute_batch("
        CREATE TABLE IF NOT EXISTS runtime_logs (
            id       INTEGER PRIMARY KEY AUTOINCREMENT,
            ts       TEXT NOT NULL DEFAULT (datetime('now', 'localtime')),
            category TEXT NOT NULL,
            level    TEXT NOT NULL,
            message  TEXT NOT NULL,
            context  TEXT
        );
    ").expect("create runtime_logs table");

    // AI 笔记（Claude 通过 MCP 或用户手动写入的分析结论）
    conn.execute_batch("
        CREATE TABLE IF NOT EXISTS project_notes (
            id         INTEGER PRIMARY KEY AUTOINCREMENT,
            project_id TEXT NOT NULL,
            source     TEXT NOT NULL DEFAULT 'mcp',
            content    TEXT NOT NULL,
            created_at TEXT DEFAULT (datetime('now', 'localtime'))
        );
    ").expect("create project_notes table");

    // 用户项目个人备忘（自由文本待办，Claude 不感知）
    conn.execute_batch("
        CREATE TABLE IF NOT EXISTS user_project_todos (
            id         INTEGER PRIMARY KEY AUTOINCREMENT,
            project_id TEXT NOT NULL,
            content    TEXT NOT NULL,
            done       INTEGER NOT NULL DEFAULT 0,
            sort_order INTEGER NOT NULL DEFAULT 0,
            created_at TEXT DEFAULT (datetime('now', 'localtime'))
        );
    ").expect("create user_project_todos table");

    // 蓝图 Buff：记录哪些项目施加了 buff
    conn.execute_batch("
        CREATE TABLE IF NOT EXISTS blueprint_buffs (
            project_path TEXT PRIMARY KEY,
            project_id   TEXT NOT NULL,
            applied_at   TEXT NOT NULL DEFAULT (datetime('now')),
            last_sync_at TEXT,
            last_commit  TEXT,
            status       TEXT NOT NULL DEFAULT 'active'
        );
    ").expect("create blueprint_buffs table");

    // 飞轮 L0：从 git 解析的 conventional commit 执行质量指标
    conn.execute_batch("
        CREATE TABLE IF NOT EXISTS commit_metrics (
            sha          TEXT PRIMARY KEY,
            project_id   TEXT NOT NULL,
            commit_type  TEXT,
            scope        TEXT,
            is_rework    INTEGER NOT NULL DEFAULT 0,
            is_ci_auto   INTEGER NOT NULL DEFAULT 0,
            committed_at TEXT,
            parsed_at    TEXT NOT NULL DEFAULT (datetime('now'))
        );
        CREATE INDEX IF NOT EXISTS idx_commit_metrics_project ON commit_metrics(project_id);
    ").expect("create commit_metrics table");

    // 增量迁移（飞轮 R1）：Rules-Applied trailer 规则命中列（已存在则忽略）
    let _ = conn.execute("ALTER TABLE commit_metrics ADD COLUMN rules_applied TEXT", []);

    // 仪表盘全局快启应用（无项目 FK 约束）
    conn.execute_batch("
        CREATE TABLE IF NOT EXISTS global_tools (
            id         TEXT PRIMARY KEY,
            name       TEXT NOT NULL,
            icon       TEXT NOT NULL DEFAULT '🔧',
            exe_path   TEXT NOT NULL,
            args       TEXT,
            sort_order INTEGER DEFAULT 0
        );
    ").expect("create global_tools table");

    // 增量迁移：模板 variants 列（多初始化方式下拉选项）
    let _ = conn.execute_batch(
        "ALTER TABLE project_templates ADD COLUMN variants TEXT;"
    );

    // 增量迁移：服务器注册表 + 项目-服务器关联
    conn.execute_batch("
        CREATE TABLE IF NOT EXISTS servers (
            id           TEXT PRIMARY KEY,
            name         TEXT NOT NULL,
            server_host  TEXT NOT NULL,
            server_port  TEXT DEFAULT '22',
            username     TEXT,
            password     TEXT,
            ssh_key_path TEXT,
            quick_links  TEXT,
            notes        TEXT,
            created_at   TEXT DEFAULT (datetime('now')),
            updated_at   TEXT DEFAULT (datetime('now'))
        );
        CREATE TABLE IF NOT EXISTS project_server_map (
            project_id     TEXT NOT NULL,
            server_id      TEXT NOT NULL,
            role           TEXT DEFAULT 'production',
            deploy_type    TEXT DEFAULT 'bare',
            deploy_path    TEXT,
            container_name TEXT,
            start_command  TEXT,
            notes          TEXT,
            PRIMARY KEY (project_id, server_id)
        );
    ").expect("create servers tables");

    // 增量迁移：project_server_map 新增部署类型字段（已存在则忽略）
    let _ = conn.execute("ALTER TABLE project_server_map ADD COLUMN deploy_type TEXT DEFAULT 'bare'", []);
    let _ = conn.execute("ALTER TABLE project_server_map ADD COLUMN container_name TEXT", []);
    let _ = conn.execute("ALTER TABLE project_server_map ADD COLUMN start_command TEXT", []);

    // 增量迁移：GitHub 镜像源 + 服务器网络配置 + 软件仓库
    conn.execute_batch("
        CREATE TABLE IF NOT EXISTS github_mirrors (
            id          TEXT PRIMARY KEY,
            name        TEXT NOT NULL,
            url_prefix  TEXT NOT NULL,
            is_builtin  INTEGER DEFAULT 0,
            sort_order  INTEGER DEFAULT 0
        );
        CREATE TABLE IF NOT EXISTS server_network_config (
            server_id        TEXT PRIMARY KEY,
            github_mirror_id TEXT,
            last_tested      TEXT,
            test_results     TEXT
        );
        CREATE TABLE IF NOT EXISTS server_software (
            id             TEXT PRIMARY KEY,
            name           TEXT NOT NULL,
            description    TEXT,
            category       TEXT,
            install_type   TEXT NOT NULL DEFAULT 'package',
            install_cmd    TEXT,
            github_url     TEXT,
            remote_path    TEXT,
            post_cmd       TEXT,
            is_builtin     INTEGER DEFAULT 0,
            sort_order     INTEGER DEFAULT 0,
            created_at     TEXT DEFAULT (datetime('now'))
        );
    ").expect("create software mgmt tables");

    // 软件分类标签
    conn.execute_batch("
        CREATE TABLE IF NOT EXISTS software_categories (
            id         TEXT PRIMARY KEY,
            name       TEXT NOT NULL,
            color      TEXT NOT NULL DEFAULT '#6b7280',
            sort_order INTEGER DEFAULT 0
        );
    ").expect("create software_categories table");

    // 内置分类标签（幂等）
    conn.execute_batch("
        INSERT OR IGNORE INTO software_categories (id, name, color, sort_order)
        VALUES ('cat-web',     'Web 服务',  '#3b82f6', 0);
        INSERT OR IGNORE INTO software_categories (id, name, color, sort_order)
        VALUES ('cat-runtime', '运行时',    '#10b981', 1);
        INSERT OR IGNORE INTO software_categories (id, name, color, sort_order)
        VALUES ('cat-tool',    '工具',      '#f59e0b', 2);
        INSERT OR IGNORE INTO software_categories (id, name, color, sort_order)
        VALUES ('cat-custom',  '自定义',    '#8b5cf6', 3);
    ").expect("seed software categories");

    // 迁移旧 category 字符串 → category id
    let _ = conn.execute_batch("
        UPDATE server_software SET category = 'cat-web'     WHERE category = 'web';
        UPDATE server_software SET category = 'cat-runtime'  WHERE category = 'runtime';
        UPDATE server_software SET category = 'cat-tool'     WHERE category = 'tool';
        UPDATE server_software SET category = 'cat-custom'   WHERE category = 'custom';
    ");

    // 内置镜像源（幂等）
    conn.execute_batch("
        INSERT OR IGNORE INTO github_mirrors (id, name, url_prefix, is_builtin, sort_order)
        VALUES ('mirror-direct', '直连（无镜像）', 'https://github.com', 1, 0);
        INSERT OR IGNORE INTO github_mirrors (id, name, url_prefix, is_builtin, sort_order)
        VALUES ('mirror-ghfast', 'ghfast.top', 'https://ghfast.top', 1, 1);
        INSERT OR IGNORE INTO github_mirrors (id, name, url_prefix, is_builtin, sort_order)
        VALUES ('mirror-ghproxy', 'gh-proxy.com', 'https://gh-proxy.com', 1, 2);
    ").expect("seed github mirrors");

    // 内置软件模板（幂等）
    conn.execute_batch("
        INSERT OR IGNORE INTO server_software (id, name, description, category, install_type, install_cmd, is_builtin, sort_order)
        VALUES ('sw-nginx',  'Nginx',    'Web 服务器 / 反向代理', 'cat-web',     'package', 'apt install -y nginx && systemctl enable nginx',  1, 0);
        INSERT OR IGNORE INTO server_software (id, name, description, category, install_type, install_cmd, is_builtin, sort_order)
        VALUES ('sw-docker', 'Docker',   '容器运行时',            'cat-runtime', 'package', 'apt install -y docker.io && systemctl enable docker', 1, 1);
        INSERT OR IGNORE INTO server_software (id, name, description, category, install_type, install_cmd, is_builtin, sort_order)
        VALUES ('sw-node20', 'Node.js 20','JavaScript 运行时',    'cat-runtime', 'package', 'curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt install -y nodejs', 1, 2);
        INSERT OR IGNORE INTO server_software (id, name, description, category, install_type, install_cmd, is_builtin, sort_order)
        VALUES ('sw-pm2',    'PM2',      'Node.js 进程管理',      'cat-runtime', 'package', 'npm install -g pm2', 1, 3);
        INSERT OR IGNORE INTO server_software (id, name, description, category, install_type, install_cmd, is_builtin, sort_order)
        VALUES ('sw-git',    'Git',      '版本控制',              'cat-tool',    'package', 'apt install -y git', 1, 4);
        INSERT OR IGNORE INTO server_software (id, name, description, category, install_type, install_cmd, is_builtin, sort_order)
        VALUES ('sw-htop',   'htop',     '系统监控工具',          'cat-tool',    'package', 'apt install -y htop', 1, 5);
    ").expect("seed server software");

    // 增量迁移：服务连接配置（每台服务器上部署的各种服务的连接信息）
    conn.execute_batch("
        CREATE TABLE IF NOT EXISTS server_service_configs (
            id          TEXT PRIMARY KEY,
            server_id   TEXT NOT NULL,
            name        TEXT NOT NULL,
            category    TEXT DEFAULT 'custom',
            scope       TEXT DEFAULT 'system',
            config_data TEXT NOT NULL DEFAULT '[]',
            sort_order  INTEGER DEFAULT 0,
            created_at  TEXT DEFAULT (datetime('now')),
            updated_at  TEXT DEFAULT (datetime('now'))
        );
    ").expect("create server_service_configs table");
    let _ = conn.execute("ALTER TABLE server_service_configs ADD COLUMN scope TEXT DEFAULT 'system'", []);

    // 增量迁移：API 密钥管理
    conn.execute_batch("
        CREATE TABLE IF NOT EXISTS api_keys (
            id          TEXT PRIMARY KEY,
            name        TEXT NOT NULL,
            provider    TEXT NOT NULL,
            api_key     TEXT NOT NULL,
            base_url    TEXT,
            model       TEXT,
            notes       TEXT,
            created_at  TEXT DEFAULT (datetime('now')),
            updated_at  TEXT DEFAULT (datetime('now'))
        );
    ").expect("create api_keys table");

    // 增量迁移：宿主机部署应用
    conn.execute_batch("
        CREATE TABLE IF NOT EXISTS server_host_apps (
            id            TEXT PRIMARY KEY,
            server_id     TEXT NOT NULL,
            name          TEXT NOT NULL,
            deploy_type   TEXT NOT NULL DEFAULT 'systemd',
            service_name  TEXT,
            binary_path   TEXT,
            config_path   TEXT,
            log_path      TEXT,
            port          TEXT,
            start_cmd     TEXT,
            stop_cmd      TEXT,
            restart_cmd   TEXT,
            status_cmd    TEXT,
            notes         TEXT,
            sort_order    INTEGER DEFAULT 0,
            created_at    TEXT DEFAULT (datetime('now')),
            updated_at    TEXT DEFAULT (datetime('now'))
        );
    ").expect("create server_host_apps table");

    // ── 源码库 ──────────────────────────────────────────────────────────
    conn.execute_batch("
        CREATE TABLE IF NOT EXISTS source_categories (
            id         TEXT PRIMARY KEY,
            name       TEXT NOT NULL,
            icon       TEXT DEFAULT '📁',
            color      TEXT DEFAULT '#6B7280',
            sort_order INTEGER DEFAULT 0
        );

        CREATE TABLE IF NOT EXISTS source_projects (
            id            TEXT PRIMARY KEY,
            name          TEXT NOT NULL,
            description   TEXT,
            category_id   TEXT REFERENCES source_categories(id) ON DELETE SET NULL,
            language      TEXT,
            tags          TEXT,
            repo_url      TEXT,
            introduction  TEXT,
            readme        TEXT,
            notes         TEXT,
            created_at    TEXT DEFAULT (datetime('now')),
            updated_at    TEXT DEFAULT (datetime('now'))
        );

        CREATE TABLE IF NOT EXISTS source_versions (
            id           TEXT PRIMARY KEY,
            project_id   TEXT NOT NULL REFERENCES source_projects(id) ON DELETE CASCADE,
            version      TEXT NOT NULL,
            file_path    TEXT NOT NULL,
            archive_path TEXT,
            file_size    INTEGER DEFAULT 0,
            is_primary   INTEGER DEFAULT 0,
            dir_tree     TEXT,
            notes        TEXT,
            imported_at  TEXT DEFAULT (datetime('now'))
        );
    ").expect("create source_library tables");

    // 增量迁移：项目历史快照（每日一条，供 Claude 做趋势分析）
    conn.execute_batch("
        CREATE TABLE IF NOT EXISTS project_snapshots (
            id                    INTEGER PRIMARY KEY AUTOINCREMENT,
            project_id            TEXT NOT NULL,
            snapshot_date         TEXT NOT NULL,
            days_since_commit     INTEGER,
            blueprint_done_rate   REAL,
            blueprint_in_progress INTEGER DEFAULT 0,
            blueprint_blocked     INTEGER DEFAULT 0,
            has_errors            INTEGER DEFAULT 0,
            created_at            TEXT DEFAULT (datetime('now')),
            UNIQUE(project_id, snapshot_date)
        );
    ").expect("create project_snapshots table");

    // 增量迁移：云产品管理
    conn.execute_batch("
        CREATE TABLE IF NOT EXISTS cloud_products (
            id           TEXT PRIMARY KEY,
            vendor       TEXT NOT NULL DEFAULT 'aliyun',
            product_type TEXT NOT NULL DEFAULT 'ecs',
            name         TEXT NOT NULL,
            region       TEXT,
            instance_id  TEXT,
            endpoint     TEXT,
            expire_at    TEXT,
            status       TEXT NOT NULL DEFAULT 'active',
            config_data  TEXT NOT NULL DEFAULT '{}',
            notes        TEXT,
            sort_order   INTEGER DEFAULT 0,
            created_at   TEXT DEFAULT (datetime('now')),
            updated_at   TEXT DEFAULT (datetime('now'))
        );
        CREATE TABLE IF NOT EXISTS cloud_db_databases (
            id          TEXT PRIMARY KEY,
            product_id  TEXT NOT NULL REFERENCES cloud_products(id) ON DELETE CASCADE,
            name        TEXT NOT NULL,
            username    TEXT,
            password    TEXT,
            charset     TEXT DEFAULT 'utf8mb4',
            notes       TEXT,
            sort_order  INTEGER DEFAULT 0,
            created_at  TEXT DEFAULT (datetime('now')),
            updated_at  TEXT DEFAULT (datetime('now'))
        );
    ").expect("create cloud_products table");

    // 增量迁移：cloud_db_databases 加 db_driver 字段
    let _ = conn.execute(
        "ALTER TABLE cloud_db_databases ADD COLUMN db_driver TEXT NOT NULL DEFAULT 'mysql'",
        [],
    );

    // 增量迁移：Docker 容器应用管理
    conn.execute_batch("
        CREATE TABLE IF NOT EXISTS docker_apps (
            id          TEXT PRIMARY KEY,
            product_id  TEXT NOT NULL REFERENCES cloud_products(id) ON DELETE CASCADE,
            name        TEXT NOT NULL,
            access_url  TEXT,
            username    TEXT,
            password    TEXT,
            notes       TEXT,
            sort_order  INTEGER DEFAULT 0,
            created_at  TEXT DEFAULT (datetime('now')),
            updated_at  TEXT DEFAULT (datetime('now'))
        );
    ").expect("create docker_apps table");

    // 增量迁移：Agent 基础设施文档腐化评分缓存
    conn.execute_batch("
        CREATE TABLE IF NOT EXISTS doc_freshness_cache (
            project_id  TEXT PRIMARY KEY,
            score       TEXT NOT NULL,
            details     TEXT NOT NULL,
            checked_at  TEXT NOT NULL DEFAULT (datetime('now'))
        );
    ").expect("create doc_freshness_cache table");

    // 增量迁移：Gitea 实例 + 项目-仓库绑定
    conn.execute_batch("
        CREATE TABLE IF NOT EXISTS gitea_instances (
            id           TEXT PRIMARY KEY,
            name         TEXT NOT NULL,
            url          TEXT NOT NULL,
            access_token TEXT NOT NULL,
            is_default   INTEGER NOT NULL DEFAULT 0,
            created_at   TEXT DEFAULT (datetime('now'))
        );

        CREATE TABLE IF NOT EXISTS gitea_repos (
            id              TEXT PRIMARY KEY,
            instance_id     TEXT NOT NULL REFERENCES gitea_instances(id) ON DELETE CASCADE,
            project_id      TEXT NOT NULL,
            gitea_owner     TEXT NOT NULL,
            gitea_repo_name TEXT NOT NULL,
            clone_url       TEXT,
            webhook_secret  TEXT,
            webhook_id      INTEGER,
            linked_at       TEXT DEFAULT (datetime('now'))
        );
        CREATE UNIQUE INDEX IF NOT EXISTS idx_gitea_repos_project ON gitea_repos(project_id);
    ").expect("create gitea tables");
}

#[cfg(test)]
pub fn conn_with_schema() -> rusqlite::Connection {
    let conn = rusqlite::Connection::open_in_memory().unwrap();
    conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;")
        .unwrap();
    migrate(&conn);
    conn
}

/// 写入运行时日志。失败时静默（不能让日志写入反过来崩溃业务逻辑）
pub fn log_event(category: &str, level: &str, message: &str, context: Option<&str>) {
    let pool = match POOL.get() {
        Some(p) => p,
        None => return,
    };
    let conn = match pool.get() {
        Ok(c) => c,
        Err(_) => return,
    };
    let _ = conn.execute(
        "INSERT INTO runtime_logs (category, level, message, context) VALUES (?1, ?2, ?3, ?4)",
        rusqlite::params![category, level, message, context],
    );
    // 超出 550 条时才触发清理，保留最新 500 条（避免每次写入都全表扫描）
    let count: i64 = conn
        .query_row("SELECT COUNT(*) FROM runtime_logs", [], |r| r.get(0))
        .unwrap_or(0);
    if count > 550 {
        let _ = conn.execute(
            "DELETE FROM runtime_logs WHERE id NOT IN (SELECT id FROM runtime_logs ORDER BY id DESC LIMIT 500)",
            [],
        );
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn migrate_idempotent() {
        // 连续两次 migrate 不应 panic
        let conn = conn_with_schema();
        migrate(&conn);
    }

    #[test]
    fn core_tables_exist() {
        let conn = conn_with_schema();
        let count: i64 = conn
            .query_row(
                "SELECT COUNT(*) FROM sqlite_master WHERE type='table' \
                 AND name IN ('projects','settings','spaces','git_repos','runtime_logs',\
                              'project_profiles','project_workspaces')",
                [],
                |r| r.get(0),
            )
            .unwrap();
        assert_eq!(count, 7, "缺少核心表");
    }

    #[test]
    fn default_settings_seeded() {
        let conn = conn_with_schema();

        let editor: String = conn
            .query_row("SELECT value FROM settings WHERE key='editor'", [], |r| r.get(0))
            .unwrap();
        assert_eq!(editor, "cursor");

        let port: String = conn
            .query_row("SELECT value FROM settings WHERE key='mcp_port'", [], |r| r.get(0))
            .unwrap();
        assert_eq!(port, "27190");
    }

    #[test]
    fn runtime_logs_insert_and_count() {
        let conn = conn_with_schema();
        conn.execute(
            "INSERT INTO runtime_logs (category, level, message) VALUES ('test','info','hello')",
            [],
        )
        .unwrap();
        let count: i64 = conn
            .query_row("SELECT COUNT(*) FROM runtime_logs", [], |r| r.get(0))
            .unwrap();
        assert_eq!(count, 1);
    }

    #[test]
    fn log_event_silent_when_no_pool() {
        // pool 未初始化时应静默返回，不 panic
        log_event("test", "info", "no-pool test", None);
    }
}
