from __future__ import annotations

import json
import secrets
import sqlite3
from contextlib import contextmanager
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Iterator


def utc_now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


class Storage:
    def __init__(self, db_path: str) -> None:
        self.db_path = Path(db_path)
        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        self._init_db()

    @contextmanager
    def connect(self) -> Iterator[sqlite3.Connection]:
        connection = sqlite3.connect(self.db_path)
        connection.row_factory = sqlite3.Row
        try:
            yield connection
            connection.commit()
        finally:
            connection.close()

    def _init_db(self) -> None:
        with self.connect() as conn:
            conn.executescript(
                """
                CREATE TABLE IF NOT EXISTS link_sessions (
                    link_token TEXT PRIMARY KEY,
                    player_id TEXT NOT NULL,
                    state_token TEXT NOT NULL UNIQUE,
                    status TEXT NOT NULL,
                    created_at TEXT NOT NULL,
                    updated_at TEXT NOT NULL,
                    completed_at TEXT,
                    error_message TEXT
                );

                CREATE TABLE IF NOT EXISTS player_links (
                    player_id TEXT PRIMARY KEY,
                    patreon_user_id TEXT NOT NULL,
                    access_token TEXT NOT NULL,
                    refresh_token TEXT NOT NULL,
                    token_expires_at TEXT NOT NULL,
                    scope TEXT NOT NULL,
                    entitlement_json TEXT NOT NULL,
                    created_at TEXT NOT NULL,
                    updated_at TEXT NOT NULL
                );
                """
            )

    def create_link_session(self, player_id: str) -> dict[str, str]:
        now = utc_now_iso()
        session = {
            "link_token": secrets.token_urlsafe(32),
            "state_token": secrets.token_urlsafe(32),
            "player_id": player_id,
            "status": "pending",
            "created_at": now,
            "updated_at": now,
        }
        with self.connect() as conn:
            conn.execute(
                """
                INSERT INTO link_sessions (
                    link_token, player_id, state_token, status, created_at, updated_at
                ) VALUES (?, ?, ?, ?, ?, ?)
                """,
                (
                    session["link_token"],
                    session["player_id"],
                    session["state_token"],
                    session["status"],
                    session["created_at"],
                    session["updated_at"],
                ),
            )
        return session

    def get_link_session_by_token(self, link_token: str) -> dict[str, Any] | None:
        with self.connect() as conn:
            row = conn.execute(
                "SELECT * FROM link_sessions WHERE link_token = ?",
                (link_token,),
            ).fetchone()
        return dict(row) if row else None

    def get_link_session_by_state(self, state_token: str) -> dict[str, Any] | None:
        with self.connect() as conn:
            row = conn.execute(
                "SELECT * FROM link_sessions WHERE state_token = ?",
                (state_token,),
            ).fetchone()
        return dict(row) if row else None

    def update_link_session_status(
        self,
        link_token: str,
        *,
        status: str,
        error_message: str | None = None,
        completed: bool = False,
    ) -> None:
        now = utc_now_iso()
        completed_at = now if completed else None
        with self.connect() as conn:
            conn.execute(
                """
                UPDATE link_sessions
                SET status = ?, updated_at = ?, completed_at = COALESCE(?, completed_at), error_message = ?
                WHERE link_token = ?
                """,
                (status, now, completed_at, error_message, link_token),
            )

    def upsert_player_link(
        self,
        *,
        player_id: str,
        patreon_user_id: str,
        access_token: str,
        refresh_token: str,
        token_expires_at: str,
        scope: str,
        entitlement: dict[str, Any],
    ) -> None:
        now = utc_now_iso()
        entitlement_json = json.dumps(entitlement, separators=(",", ":"), sort_keys=True)
        with self.connect() as conn:
            conn.execute(
                """
                INSERT INTO player_links (
                    player_id, patreon_user_id, access_token, refresh_token, token_expires_at,
                    scope, entitlement_json, created_at, updated_at
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                ON CONFLICT(player_id) DO UPDATE SET
                    patreon_user_id=excluded.patreon_user_id,
                    access_token=excluded.access_token,
                    refresh_token=excluded.refresh_token,
                    token_expires_at=excluded.token_expires_at,
                    scope=excluded.scope,
                    entitlement_json=excluded.entitlement_json,
                    updated_at=excluded.updated_at
                """,
                (
                    player_id,
                    patreon_user_id,
                    access_token,
                    refresh_token,
                    token_expires_at,
                    scope,
                    entitlement_json,
                    now,
                    now,
                ),
            )

    def get_player_link(self, player_id: str) -> dict[str, Any] | None:
        with self.connect() as conn:
            row = conn.execute(
                "SELECT * FROM player_links WHERE player_id = ?",
                (player_id,),
            ).fetchone()
        if not row:
            return None
        record = dict(row)
        record["entitlement"] = json.loads(record.pop("entitlement_json"))
        return record
