# token_store.py
import os
import re
import json
import sqlite3
import time
from typing import Optional, Dict, Any, List

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
TOKENS_DB = os.path.join(BASE_DIR, "tiktok_tokens.sqlite3")


def _db() -> sqlite3.Connection:
    conn = sqlite3.connect(TOKENS_DB, timeout=30)
    conn.row_factory = sqlite3.Row
    # Mejor concurrencia para Flask/Gunicorn
    try:
        conn.execute("PRAGMA journal_mode=WAL;")
        conn.execute("PRAGMA synchronous=NORMAL;")
    except Exception:
        pass
    return conn


def _sanitize_alias(alias: str) -> str:
    """
    Alias “humano” (p.ej. nombre del canal) pero seguro para usar como key.
    - lower
    - espacios -> '-'
    - solo [a-z0-9_-]
    """
    a = (alias or "").strip().lower()
    a = re.sub(r"\s+", "-", a)
    a = re.sub(r"[^a-z0-9_-]", "", a)
    return a or "default"


def _extract_creator_meta(token_bundle: Dict[str, Any]) -> Dict[str, Optional[str]]:
    """
    TikTok suele devolver open_id en token exchange.
    El nickname/username normalmente lo obtienes con creator_info; si lo incluyes
    en token_bundle bajo estas keys, lo persistimos también.
    """
    open_id = (token_bundle.get("open_id") or token_bundle.get("creator_open_id") or "").strip() or None
    username = (token_bundle.get("creator_username") or "").strip() or None
    nickname = (token_bundle.get("creator_nickname") or "").strip() or None
    return {
        "creator_open_id": open_id,
        "creator_username": username,
        "creator_nickname": nickname,
    }


def init_token_db() -> None:
    """
    Crea tabla y migra columnas si vienes de una versión anterior.
    """
    conn = _db()
    cur = conn.cursor()

    cur.execute(
        """
        CREATE TABLE IF NOT EXISTS tiktok_accounts (
          alias TEXT PRIMARY KEY,
          token_json TEXT NOT NULL,
          updated_at_ts INTEGER NOT NULL
        )
        """
    )
    conn.commit()

    # Migración “soft”: añadir columnas si no existen
    cur.execute("PRAGMA table_info(tiktok_accounts)")
    existing = {row["name"] for row in cur.fetchall()}

    alters = []
    if "creator_open_id" not in existing:
        alters.append("ALTER TABLE tiktok_accounts ADD COLUMN creator_open_id TEXT")
    if "creator_username" not in existing:
        alters.append("ALTER TABLE tiktok_accounts ADD COLUMN creator_username TEXT")
    if "creator_nickname" not in existing:
        alters.append("ALTER TABLE tiktok_accounts ADD COLUMN creator_nickname TEXT")

    for sql in alters:
        cur.execute(sql)

    # Índices útiles (no obligatorios)
    cur.execute("CREATE INDEX IF NOT EXISTS idx_tiktok_accounts_updated ON tiktok_accounts(updated_at_ts)")
    cur.execute("CREATE INDEX IF NOT EXISTS idx_tiktok_accounts_openid ON tiktok_accounts(creator_open_id)")

    conn.commit()
    conn.close()


def save_token(alias: str, token_bundle: Dict[str, Any]) -> str:
    """
    Guarda/actualiza el token_bundle para un alias.
    Devuelve el alias saneado realmente usado.
    """
    alias_s = _sanitize_alias(alias)
    meta = _extract_creator_meta(token_bundle)

    conn = _db()
    cur = conn.cursor()
    cur.execute(
        """
        INSERT INTO tiktok_accounts(alias, token_json, updated_at_ts, creator_open_id, creator_username, creator_nickname)
        VALUES (?, ?, ?, ?, ?, ?)
        ON CONFLICT(alias) DO UPDATE SET
          token_json=excluded.token_json,
          updated_at_ts=excluded.updated_at_ts,
          creator_open_id=COALESCE(excluded.creator_open_id, tiktok_accounts.creator_open_id),
          creator_username=COALESCE(excluded.creator_username, tiktok_accounts.creator_username),
          creator_nickname=COALESCE(excluded.creator_nickname, tiktok_accounts.creator_nickname)
        """,
        (
            alias_s,
            json.dumps(token_bundle, ensure_ascii=False),
            int(time.time()),
            meta["creator_open_id"],
            meta["creator_username"],
            meta["creator_nickname"],
        ),
    )
    conn.commit()
    conn.close()
    return alias_s


def update_account_meta(
    alias: str,
    *,
    creator_open_id: Optional[str] = None,
    creator_username: Optional[str] = None,
    creator_nickname: Optional[str] = None,
) -> None:
    """
    Útil si tras OAuth llamas a creator_info y quieres persistir nickname/username.
    """
    alias_s = _sanitize_alias(alias)
    conn = _db()
    cur = conn.cursor()
    cur.execute(
        """
        UPDATE tiktok_accounts
        SET creator_open_id=COALESCE(?, creator_open_id),
            creator_username=COALESCE(?, creator_username),
            creator_nickname=COALESCE(?, creator_nickname),
            updated_at_ts=?
        WHERE alias=?
        """,
        (
            (creator_open_id or "").strip() or None,
            (creator_username or "").strip() or None,
            (creator_nickname or "").strip() or None,
            int(time.time()),
            alias_s,
        ),
    )
    conn.commit()
    conn.close()


def load_token(alias: str) -> Optional[Dict[str, Any]]:
    """
    Carga token por alias.

    Extra UX:
    - si piden alias="default" y NO existe:
        - si SOLO hay 1 cuenta guardada -> devuelve esa
        - si hay 0 o >1 -> None
    """
    alias_s = _sanitize_alias(alias)
    conn = _db()
    cur = conn.cursor()

    cur.execute("SELECT token_json FROM tiktok_accounts WHERE alias=?", (alias_s,))
    row = cur.fetchone()

    if not row and alias_s == "default":
        cur.execute("SELECT token_json FROM tiktok_accounts ORDER BY updated_at_ts DESC LIMIT 2")
        rows = cur.fetchall()
        if len(rows) == 1:
            row = rows[0]

    conn.close()

    if not row:
        return None

    try:
        return json.loads(row["token_json"])
    except Exception:
        return None


def load_account(alias: str) -> Optional[Dict[str, Any]]:
    """
    Devuelve token + meta, útil para UI.
    """
    alias_s = _sanitize_alias(alias)
    conn = _db()
    cur = conn.cursor()
    cur.execute(
        """
        SELECT alias, token_json, updated_at_ts, creator_open_id, creator_username, creator_nickname
        FROM tiktok_accounts
        WHERE alias=?
        """,
        (alias_s,),
    )
    row = cur.fetchone()
    conn.close()

    if not row:
        return None

    try:
        token = json.loads(row["token_json"])
    except Exception:
        token = None

    return {
        "alias": row["alias"],
        "updated_at_ts": row["updated_at_ts"],
        "creator_open_id": row.get("creator_open_id"),
        "creator_username": row.get("creator_username"),
        "creator_nickname": row.get("creator_nickname"),
        "token": token,
    }


def list_accounts() -> List[Dict[str, Any]]:
    """
    Lista cuentas guardadas (para selector en UI).
    """
    conn = _db()
    cur = conn.cursor()
    cur.execute(
        """
        SELECT alias, updated_at_ts, creator_open_id, creator_username, creator_nickname
        FROM tiktok_accounts
        ORDER BY updated_at_ts DESC
        """
    )
    rows = cur.fetchall()
    conn.close()

    out: List[Dict[str, Any]] = []
    for r in rows:
        out.append(
            {
                "alias": r["alias"],
                "updated_at_ts": r["updated_at_ts"],
                "creator_open_id": r.get("creator_open_id"),
                "creator_username": r.get("creator_username"),
                "creator_nickname": r.get("creator_nickname"),
            }
        )
    return out
