from __future__ import annotations

import os
import sqlite3
import time
from pathlib import Path
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union

from store_db.schema_v1 import migrations_v1
from store_db.seed import ensure_starter

DEFAULT_DB_REL = Path(".config") / "store-suite" / "store.sqlite3"
SCHEMA_VERSION = 1


def default_db_path() -> Path:
    base = Path.home() / DEFAULT_DB_REL
    base.parent.mkdir(parents=True, exist_ok=True)
    return base


def get_db_path() -> Path:
    p = os.environ.get("STORE_DB_PATH")
    if p:
        path = Path(p).expanduser()
        path.parent.mkdir(parents=True, exist_ok=True)
        return path
    return default_db_path()


def _pragma(conn: sqlite3.Connection) -> None:
    conn.row_factory = sqlite3.Row
    conn.execute("PRAGMA foreign_keys=ON")
    conn.execute("PRAGMA journal_mode=WAL")
    conn.execute("PRAGMA busy_timeout=5000")


def connect(*, path: Optional[Path] = None, timeout: float = 5.0) -> sqlite3.Connection:
    db_path = path or get_db_path()
    conn = sqlite3.connect(str(db_path), timeout=timeout)
    _pragma(conn)
    migrate(conn)
    ensure_starter(conn)
    return conn


def with_retry(fn, *, retries: int = 3, base_sleep_s: float = 0.05):
    last = None
    for i in range(retries + 1):
        try:
            return fn()
        except sqlite3.OperationalError as e:
            msg = str(e).lower()
            if "database is locked" not in msg and "database schema is locked" not in msg:
                raise
            last = e
            if i >= retries:
                raise
            time.sleep(base_sleep_s * (3**i))
    raise last  # pragma: no cover


def migrate(conn: sqlite3.Connection) -> None:
    conn.executescript(
        """
        CREATE TABLE IF NOT EXISTS meta_schema_version (
            version INTEGER NOT NULL,
            updated_at TEXT NOT NULL DEFAULT (datetime('now','localtime'))
        );
        """
    )
    row = conn.execute(
        "SELECT version FROM meta_schema_version ORDER BY updated_at DESC LIMIT 1"
    ).fetchone()
    cur_version = int(row["version"]) if row else 0
    if cur_version >= SCHEMA_VERSION:
        return
    _apply_migrations(conn, migrations_v1())
    conn.execute("INSERT INTO meta_schema_version (version) VALUES (?)", (SCHEMA_VERSION,))
    conn.commit()


def _apply_migrations(conn: sqlite3.Connection, migrations: Iterable[str]) -> None:
    for sql in migrations:
        conn.executescript(sql)


def list_tables(conn: sqlite3.Connection) -> List[str]:
    rows = conn.execute(
        "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name"
    ).fetchall()
    return [r["name"] for r in rows]
