"""SQLite 数据库连接与初始化（分库）

- assets.db ：Provider / Asset / VPSDetail / DomainDetail / AIAccount / Account
- metrics.db：MetricPoint / ServerInfo / SecurityCheck / EventLog
"""

from pathlib import Path
from typing import Generator

from sqlmodel import Session, SQLModel, create_engine

BASE_DIR = Path(__file__).resolve().parent.parent
DATA_DIR = BASE_DIR / "data"
DATA_DIR.mkdir(parents=True, exist_ok=True)

ASSETS_DB_URL = f"sqlite:///{DATA_DIR / 'assets.db'}"
METRICS_DB_URL = f"sqlite:///{DATA_DIR / 'metrics.db'}"

# timeout：sqlite3 内置 busy 等待（秒），随连接创建生效。
# SQLite 默认使用 NullPool（每请求新建连接），PRAGMA busy_timeout 只在单连接有效，
# 必须通过 connect_args 传递，否则并发写入（Agent 上报 + 清理任务）会报 database is locked。
assets_engine = create_engine(
    ASSETS_DB_URL,
    echo=False,
    connect_args={"check_same_thread": False, "timeout": 30},
)
metrics_engine = create_engine(
    METRICS_DB_URL,
    echo=False,
    connect_args={"check_same_thread": False, "timeout": 30},
)


def _enable_wal(engine) -> None:
    """启用 WAL 模式，提升 SQLite 并发读写能力（journal_mode 持久化到库文件，设置一次即可）"""
    import sqlalchemy as sa
    with engine.connect() as conn:
        conn.execute(sa.text("PRAGMA journal_mode=WAL"))


_enable_wal(assets_engine)
_enable_wal(metrics_engine)

# 兼容旧代码：默认 engine 指向资产库
engine = assets_engine


def init_db() -> None:
    """分库建表（幂等，可重复调用）"""
    from app.models.asset import Account, AIAccount, Asset, CloudflareDetail, DomainDetail, VPSDetail  # noqa: F401
    from app.models.monitor import (  # noqa: F401
        EventLog,
        MetricPoint,
        SecurityCheck,
        ServerInfo,
    )
    from app.models.provider import Provider  # noqa: F401
    from app.models.ssl import SiteCert, Subdomain  # noqa: F401

    asset_models = [Provider, Asset, VPSDetail, DomainDetail, AIAccount, CloudflareDetail, Subdomain, SiteCert, Account]
    metric_models = [MetricPoint, ServerInfo, SecurityCheck, EventLog]

    SQLModel.metadata.create_all(
        assets_engine, tables=[m.__table__ for m in asset_models]
    )
    SQLModel.metadata.create_all(
        metrics_engine, tables=[m.__table__ for m in metric_models]
    )
    _migrate_assets_db()
    _migrate_indexes()


def _migrate_assets_db() -> None:
    """轻量迁移：为已有表补充新增列（SQLite create_all 不会修改已有表结构）"""
    import sqlalchemy as sa

    with assets_engine.begin() as conn:
        insp = sa.inspect(conn)
        if insp.has_table("assets"):
            cols = {c["name"] for c in insp.get_columns("assets")}
            if "external_id" not in cols:
                conn.execute(sa.text("ALTER TABLE assets ADD COLUMN external_id VARCHAR"))
            if "renew_url" not in cols:
                conn.execute(sa.text("ALTER TABLE assets ADD COLUMN renew_url VARCHAR"))
            if "cancel_url" not in cols:
                conn.execute(sa.text("ALTER TABLE assets ADD COLUMN cancel_url VARCHAR"))
            if "account_id" not in cols:
                conn.execute(sa.text("ALTER TABLE assets ADD COLUMN account_id INTEGER"))
            # 资产账号引用从 name 字符串迁移到 account_id 外键（幂等）
            _backfill_asset_account_id(conn)
        if insp.has_table("providers"):
            cols = {c["name"] for c in insp.get_columns("providers")}
            if "last_synced_at" not in cols:
                conn.execute(sa.text("ALTER TABLE providers ADD COLUMN last_synced_at DATETIME"))
            if "services" not in cols:
                conn.execute(sa.text("ALTER TABLE providers ADD COLUMN services VARCHAR DEFAULT ''"))
            # 为已有预设平台补充 services（仅补空值，用户自定义行不动）
            _backfill_provider_services(conn)
        if insp.has_table("ai_accounts"):
            cols = {c["name"] for c in insp.get_columns("ai_accounts")}
            if "api_key_encrypted" not in cols:
                conn.execute(sa.text("ALTER TABLE ai_accounts ADD COLUMN api_key_encrypted VARCHAR"))
            # 迁移：将明文 api_key 加密后存入 api_key_encrypted，并清空原字段
            _migrate_plaintext_api_keys(conn)
        if insp.has_table("accounts"):
            cols = {c["name"] for c in insp.get_columns("accounts")}
            if "login_user" not in cols:
                conn.execute(sa.text("ALTER TABLE accounts ADD COLUMN login_user VARCHAR"))
            if "login_password_encrypted" not in cols:
                conn.execute(sa.text("ALTER TABLE accounts ADD COLUMN login_password_encrypted VARCHAR"))
            if "api_config_encrypted" not in cols:
                conn.execute(sa.text("ALTER TABLE accounts ADD COLUMN api_config_encrypted VARCHAR"))
            if "last_synced_at" not in cols:
                conn.execute(sa.text("ALTER TABLE accounts ADD COLUMN last_synced_at DATETIME"))
            # 凭证下沉：平台 api_config 迁到默认账号；AI 资产自动挂靠账号
            _backfill_account_credentials(conn)
            # 合并存量 (platform,name) 重复账号，为建唯一索引做准备
            _dedupe_accounts(conn)


def _migrate_indexes() -> None:
    """为已有数据库补充复合索引（create_all 不会为已存在的表补索引，IF NOT EXISTS 幂等）"""
    import sqlalchemy as sa

    # 旧的全局 name 唯一索引（早期 Account.name unique=True 产物）会阻止跨平台同名，
    # 与新的 (platform,name) 联合唯一冲突，需先删除
    with assets_engine.begin() as conn:
        conn.execute(sa.text("DROP INDEX IF EXISTS ix_accounts_name"))

    stmts = [
        (assets_engine, "CREATE INDEX IF NOT EXISTS ix_assets_provider_ext_type ON assets (provider_id, external_id, asset_type)"),
        (assets_engine, "CREATE INDEX IF NOT EXISTS ix_assets_account_id ON assets (account_id)"),
        # (platform, name) 联合唯一：同邮箱可跨平台复用，同平台内不重名
        (assets_engine, "CREATE UNIQUE INDEX IF NOT EXISTS uq_accounts_platform_name ON accounts (platform, name)"),
        (metrics_engine, "CREATE INDEX IF NOT EXISTS ix_metric_points_asset_ts ON metric_points (asset_id, ts)"),
        (metrics_engine, "CREATE INDEX IF NOT EXISTS ix_security_checks_asset_ts ON security_checks (asset_id, ts)"),
    ]
    for eng, sql in stmts:
        with eng.begin() as conn:
            conn.execute(sa.text(sql))


def _migrate_plaintext_api_keys(conn) -> None:
    """一次性迁移：将 ai_accounts 中残留的明文 api_key 加密后存入 api_key_encrypted，并清空原字段"""
    import sqlalchemy as sa
    from app.core.crypto import encrypt

    rows = conn.execute(
        sa.text("SELECT id, api_key FROM ai_accounts WHERE api_key IS NOT NULL AND api_key != ''")
    ).fetchall()
    if not rows:
        return
    for row in rows:
        encrypted = encrypt(row[1])
        if encrypted:
            conn.execute(
                sa.text("UPDATE ai_accounts SET api_key_encrypted = :enc, api_key = NULL WHERE id = :id"),
                {"enc": encrypted, "id": row[0]},
            )


def _backfill_provider_services(conn) -> None:
    """为已有预设平台补充 services 服务列表（只更新 services 为空的预设 slug）"""
    import sqlalchemy as sa
    from app.core.seed import PRESET_PROVIDERS

    for data in PRESET_PROVIDERS:
        services = data.get("services")
        if not services:
            continue
        conn.execute(
            sa.text(
                "UPDATE providers SET services = :services "
                "WHERE slug = :slug AND (services IS NULL OR services = '')"
            ),
            {"services": services, "slug": data["slug"]},
        )


def _dedupe_accounts(conn) -> None:
    """建 (platform,name) 唯一索引前的防护：合并存量重复账号。

    platform 统一按 COALESCE(platform,'') 规范化比较（避免 NULL 绕过）。
    重复时保留 id 最小者，将其余行的资产引用(account_id/account)与凭证并入后删除。
    当前数据量小，预期无重复；此函数仅在发现重复时产生写操作。
    """
    import sqlalchemy as sa

    rows = conn.execute(
        sa.text(
            "SELECT COALESCE(platform,'') AS p, name, COUNT(*) AS c, MIN(id) AS keep_id "
            "FROM accounts GROUP BY p, name HAVING c > 1"
        )
    ).fetchall()
    for p, name, _c, keep_id in rows:
        dups = conn.execute(
            sa.text(
                "SELECT id FROM accounts WHERE COALESCE(platform,'') = :p AND name = :n AND id != :keep"
            ),
            {"p": p, "n": name, "keep": keep_id},
        ).fetchall()
        for (dup_id,) in dups:
            # 资产引用并入保留行
            conn.execute(
                sa.text("UPDATE assets SET account_id = :keep WHERE account_id = :dup"),
                {"keep": keep_id, "dup": dup_id},
            )
            # 凭证：保留行为空时才从重复行拷贝
            conn.execute(
                sa.text(
                    "UPDATE accounts SET "
                    "api_config_encrypted = COALESCE(api_config_encrypted, (SELECT api_config_encrypted FROM accounts WHERE id = :dup)), "
                    "login_password_encrypted = COALESCE(login_password_encrypted, (SELECT login_password_encrypted FROM accounts WHERE id = :dup)), "
                    "login_user = COALESCE(login_user, (SELECT login_user FROM accounts WHERE id = :dup)) "
                    "WHERE id = :keep"
                ),
                {"keep": keep_id, "dup": dup_id},
            )
            conn.execute(sa.text("DELETE FROM accounts WHERE id = :dup"), {"dup": dup_id})


def _backfill_asset_account_id(conn) -> None:
    """资产账号引用迁移（幂等）：把 Asset.account 的 name 字符串引用回填为 account_id 外键。

    匹配规则：按 accounts.name 匹配；同名多个时优先 platform 与 asset.provider 一致者。
    匹配不到（历史自由文本/未登记账号）则跳过并保留 account 字符串，供人工处理。
    """
    import sqlalchemy as sa

    rows = conn.execute(
        sa.text(
            "SELECT id, account, provider FROM assets "
            "WHERE account_id IS NULL AND account IS NOT NULL AND account != ''"
        )
    ).fetchall()
    if not rows:
        return
    for asset_id, acc_name, provider in rows:
        # 同名账号可能多个：优先 platform 与资产 provider 一致的
        cand = conn.execute(
            sa.text(
                "SELECT id, COALESCE(platform,'') FROM accounts WHERE name = :n "
                "ORDER BY (COALESCE(platform,'') = :prov) DESC, id ASC"
            ),
            {"n": acc_name, "prov": provider or ""},
        ).fetchall()
        if cand:
            conn.execute(
                sa.text("UPDATE assets SET account_id = :aid WHERE id = :id"),
                {"aid": cand[0][0], "id": asset_id},
            )


def _backfill_account_credentials(conn) -> None:
    """凭证下沉迁移（幂等，仅在首次加列后产生效果）：

    1. 平台凭证下沉：api_config_encrypted 非空的 Provider → 确保存在
       「{slug}-默认」账号并拷入凭证（不删平台原值，保留可回滚）。
    2. AI 资产挂靠：account 为空的 ai_agent 资产 → 按 ai_accounts.provider
       找/建账号并关联；账号无 API 配置时把该资产的 api_key 写入账号配置。
    """
    import json
    from datetime import datetime, timezone

    import sqlalchemy as sa

    from app.core.crypto import decrypt, encrypt

    def _ensure_account(name: str, platform: str) -> int:
        # 按 (platform, name) 查，避免同名跨平台账号混淆
        row = conn.execute(
            sa.text("SELECT id FROM accounts WHERE name = :name AND COALESCE(platform,'') = :platform"),
            {"name": name, "platform": platform},
        ).first()
        if row:
            return row[0]
        conn.execute(
            sa.text("INSERT INTO accounts (name, platform, created_at) VALUES (:name, :platform, :ts)"),
            {"name": name, "platform": platform, "ts": datetime.now(timezone.utc).replace(tzinfo=None)},
        )
        return conn.execute(
            sa.text("SELECT id FROM accounts WHERE name = :name AND COALESCE(platform,'') = :platform"),
            {"name": name, "platform": platform},
        ).first()[0]

    # 1. 平台凭证下沉到默认账号
    for pid, slug, cfg in conn.execute(
        sa.text(
            "SELECT id, slug, api_config_encrypted FROM providers "
            "WHERE api_config_encrypted IS NOT NULL AND api_config_encrypted != ''"
        )
    ).fetchall():
        acc_name = f"{slug}-默认"
        _ensure_account(acc_name, slug)
        conn.execute(
            sa.text(
                "UPDATE accounts SET api_config_encrypted = :cfg "
                "WHERE name = :name AND COALESCE(platform,'') = :platform "
                "AND (api_config_encrypted IS NULL OR api_config_encrypted = '')"
            ),
            {"cfg": cfg, "name": acc_name, "platform": slug},
        )

    # 2. 无账号的 AI 资产按 provider 挂靠（account_id 外键 + account 字符串兼容）
    rows = conn.execute(
        sa.text(
            "SELECT a.id, COALESCE(ai.provider, '') FROM assets a "
            "JOIN ai_accounts ai ON ai.asset_id = a.id "
            "WHERE a.asset_type = 'ai_agent' AND a.account_id IS NULL AND (a.account IS NULL OR a.account = '')"
        )
    ).fetchall()
    for asset_id, provider in rows:
        if not provider:
            continue
        acc_id = _ensure_account(provider, provider)
        conn.execute(
            sa.text("UPDATE assets SET account = :acc, account_id = :aid WHERE id = :id"),
            {"acc": provider, "aid": acc_id, "id": asset_id},
        )
        # 账号尚无 API 配置时，把该资产的 api_key 写入账号配置（解密后重组 JSON 再加密）
        key_row = conn.execute(
            sa.text("SELECT api_key_encrypted FROM ai_accounts WHERE asset_id = :id"),
            {"id": asset_id},
        ).first()
        if key_row and key_row[0]:
            api_key = decrypt(key_row[0])
            if api_key:
                cfg_json = json.dumps({"api_key": api_key})
                cfg_enc = encrypt(cfg_json)
                if cfg_enc:
                    conn.execute(
                        sa.text(
                            "UPDATE accounts SET api_config_encrypted = :cfg "
                            "WHERE name = :name AND COALESCE(platform,'') = :platform "
                            "AND (api_config_encrypted IS NULL OR api_config_encrypted = '')"
                        ),
                        {"cfg": cfg_enc, "name": provider, "platform": provider},
                    )


def get_session() -> Generator[Session, None, None]:
    """资产库会话（默认）

    expire_on_commit=False：commit 后不失效对象属性，避免后续访问触发隐式
    重新加载查询；需要最新值的场景由调用方显式 session.refresh()。
    """
    with Session(assets_engine, expire_on_commit=False) as session:
        yield session


def get_metrics_session() -> Generator[Session, None, None]:
    """监控 / 日志库会话"""
    with Session(metrics_engine, expire_on_commit=False) as session:
        yield session
