"""SQLite database layer for OAuth tokens and Bybit credentials."""

import json
import sqlite3
import time

from cryptography.fernet import Fernet

from bybit_mcp_server.config import get_db_path, get_secret_key

SCHEMA_SQL = """
CREATE TABLE IF NOT EXISTS oauth_clients (
    client_id TEXT PRIMARY KEY,
    client_info_json TEXT NOT NULL,
    created_at REAL NOT NULL DEFAULT (unixepoch('now'))
);

CREATE TABLE IF NOT EXISTS oauth_auth_codes (
    code TEXT PRIMARY KEY,
    client_id TEXT NOT NULL,
    code_data_json TEXT NOT NULL,
    expires_at REAL NOT NULL,
    created_at REAL NOT NULL DEFAULT (unixepoch('now')),
    FOREIGN KEY (client_id) REFERENCES oauth_clients(client_id)
);

CREATE TABLE IF NOT EXISTS oauth_tokens (
    token TEXT PRIMARY KEY,
    token_type TEXT NOT NULL CHECK(token_type IN ('access', 'refresh')),
    client_id TEXT NOT NULL,
    scopes_json TEXT NOT NULL,
    expires_at INTEGER,
    resource TEXT,
    related_token TEXT,
    created_at REAL NOT NULL DEFAULT (unixepoch('now')),
    FOREIGN KEY (client_id) REFERENCES oauth_clients(client_id)
);

CREATE TABLE IF NOT EXISTS bybit_credentials (
    id INTEGER PRIMARY KEY CHECK(id = 1),
    api_key_encrypted BLOB NOT NULL,
    api_secret_encrypted BLOB NOT NULL,
    testnet INTEGER NOT NULL DEFAULT 1,
    mode TEXT NOT NULL DEFAULT 'read',
    recv_window INTEGER,
    updated_at REAL NOT NULL DEFAULT (unixepoch('now'))
);
"""


# --- Encryption helpers ---


def _get_fernet() -> Fernet:
    key = get_secret_key()
    if not key:
        raise RuntimeError("BYBIT_MCP_SECRET_KEY must be set for encrypted storage")
    return Fernet(key.encode() if isinstance(key, str) else key)


def encrypt_value(plaintext: str) -> bytes:
    return _get_fernet().encrypt(plaintext.encode())


def decrypt_value(ciphertext: bytes) -> str:
    return _get_fernet().decrypt(ciphertext).decode()


# --- Connection ---


def get_connection(db_path: str | None = None) -> sqlite3.Connection:
    path = db_path or get_db_path()
    conn = sqlite3.connect(path)
    conn.row_factory = sqlite3.Row
    conn.execute("PRAGMA journal_mode=WAL")
    conn.execute("PRAGMA foreign_keys=ON")
    return conn


def init_db(db_path: str | None = None) -> str:
    """Initialize database schema. Returns the db_path used."""
    path = db_path or get_db_path()
    conn = get_connection(path)
    conn.executescript(SCHEMA_SQL)
    conn.close()
    return path


# --- OAuth clients ---


def save_oauth_client(db_path: str, client_id: str, client_info_json: str) -> None:
    conn = get_connection(db_path)
    try:
        conn.execute(
            "INSERT OR REPLACE INTO oauth_clients (client_id, client_info_json) VALUES (?, ?)",
            (client_id, client_info_json),
        )
        conn.commit()
    finally:
        conn.close()


def load_oauth_client(db_path: str, client_id: str) -> dict | None:
    conn = get_connection(db_path)
    try:
        row = conn.execute("SELECT * FROM oauth_clients WHERE client_id = ?", (client_id,)).fetchone()
        return dict(row) if row else None
    finally:
        conn.close()


# --- OAuth authorization codes ---


def save_auth_code(db_path: str, code: str, client_id: str, code_data_json: str, expires_at: float) -> None:
    conn = get_connection(db_path)
    try:
        conn.execute(
            "INSERT INTO oauth_auth_codes (code, client_id, code_data_json, expires_at) VALUES (?, ?, ?, ?)",
            (code, client_id, code_data_json, expires_at),
        )
        conn.commit()
    finally:
        conn.close()


def load_auth_code(db_path: str, code: str) -> dict | None:
    conn = get_connection(db_path)
    try:
        row = conn.execute("SELECT * FROM oauth_auth_codes WHERE code = ?", (code,)).fetchone()
        if row is None:
            return None
        row_dict = dict(row)
        if row_dict["expires_at"] < time.time():
            conn.execute("DELETE FROM oauth_auth_codes WHERE code = ?", (code,))
            conn.commit()
            return None
        return row_dict
    finally:
        conn.close()


def delete_auth_code(db_path: str, code: str) -> None:
    conn = get_connection(db_path)
    try:
        conn.execute("DELETE FROM oauth_auth_codes WHERE code = ?", (code,))
        conn.commit()
    finally:
        conn.close()


# --- OAuth tokens ---


def save_token(
    db_path: str,
    token: str,
    token_type: str,
    client_id: str,
    scopes: list[str],
    expires_at: int | None = None,
    resource: str | None = None,
    related_token: str | None = None,
) -> None:
    conn = get_connection(db_path)
    try:
        conn.execute(
            "INSERT INTO oauth_tokens (token, token_type, client_id, scopes_json, expires_at, resource, related_token)"
            " VALUES (?, ?, ?, ?, ?, ?, ?)",
            (token, token_type, client_id, json.dumps(scopes), expires_at, resource, related_token),
        )
        conn.commit()
    finally:
        conn.close()


def load_token(db_path: str, token: str, token_type: str) -> dict | None:
    conn = get_connection(db_path)
    try:
        row = conn.execute(
            "SELECT * FROM oauth_tokens WHERE token = ? AND token_type = ?", (token, token_type)
        ).fetchone()
        if row is None:
            return None
        row_dict = dict(row)
        if row_dict["expires_at"] and row_dict["expires_at"] < time.time():
            conn.execute("DELETE FROM oauth_tokens WHERE token = ?", (token,))
            conn.commit()
            return None
        return row_dict
    finally:
        conn.close()


def delete_token(db_path: str, token: str) -> None:
    conn = get_connection(db_path)
    try:
        # Also delete the related token
        row = conn.execute("SELECT related_token FROM oauth_tokens WHERE token = ?", (token,)).fetchone()
        if row and row["related_token"]:
            conn.execute("DELETE FROM oauth_tokens WHERE token = ?", (row["related_token"],))
        conn.execute("DELETE FROM oauth_tokens WHERE token = ?", (token,))
        conn.commit()
    finally:
        conn.close()


# --- Bybit credentials ---


def save_bybit_credentials(
    db_path: str,
    api_key: str,
    api_secret: str,
    testnet: bool = True,
    mode: str = "read",
    recv_window: int | None = None,
) -> None:
    conn = get_connection(db_path)
    try:
        conn.execute(
            "INSERT OR REPLACE INTO bybit_credentials"
            " (id, api_key_encrypted, api_secret_encrypted, testnet, mode, recv_window, updated_at)"
            " VALUES (1, ?, ?, ?, ?, ?, ?)",
            (encrypt_value(api_key), encrypt_value(api_secret), int(testnet), mode, recv_window, time.time()),
        )
        conn.commit()
    finally:
        conn.close()


def load_bybit_credentials(db_path: str) -> dict | None:
    conn = get_connection(db_path)
    try:
        row = conn.execute("SELECT * FROM bybit_credentials WHERE id = 1").fetchone()
        if row is None:
            return None
        return {
            "api_key": decrypt_value(row["api_key_encrypted"]),
            "api_secret": decrypt_value(row["api_secret_encrypted"]),
            "testnet": bool(row["testnet"]),
            "mode": row["mode"],
            "recv_window": row["recv_window"],
        }
    finally:
        conn.close()
