"""Tests for auth/db.py — SQLite database layer."""

import os
import time

import pytest
from cryptography.fernet import Fernet


@pytest.fixture(autouse=True)
def _setup_secret_key(monkeypatch):
    key = Fernet.generate_key().decode()
    monkeypatch.setenv("BYBIT_MCP_SECRET_KEY", key)


@pytest.fixture()
def db_path(tmp_path):
    from bybit_mcp_server.auth.db import init_db

    path = str(tmp_path / "test.db")
    init_db(path)
    return path


class TestInitDb:
    def test_creates_tables(self, db_path):
        from bybit_mcp_server.auth.db import get_connection

        conn = get_connection(db_path)
        tables = conn.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()
        table_names = {row["name"] for row in tables}
        assert "oauth_clients" in table_names
        assert "oauth_auth_codes" in table_names
        assert "oauth_tokens" in table_names
        assert "bybit_credentials" in table_names
        conn.close()


class TestEncryption:
    def test_round_trip(self):
        from bybit_mcp_server.auth.db import decrypt_value, encrypt_value

        original = "test-api-key-12345"
        encrypted = encrypt_value(original)
        assert encrypted != original.encode()
        assert decrypt_value(encrypted) == original

    def test_no_secret_key_raises(self, monkeypatch):
        from bybit_mcp_server.auth.db import encrypt_value

        monkeypatch.setenv("BYBIT_MCP_SECRET_KEY", "")
        with pytest.raises(RuntimeError, match="BYBIT_MCP_SECRET_KEY"):
            encrypt_value("test")


class TestOAuthClients:
    def test_save_and_load(self, db_path):
        from bybit_mcp_server.auth.db import load_oauth_client, save_oauth_client

        save_oauth_client(db_path, "client-1", '{"client_id":"client-1"}')
        row = load_oauth_client(db_path, "client-1")
        assert row is not None
        assert row["client_id"] == "client-1"
        assert row["client_info_json"] == '{"client_id":"client-1"}'

    def test_load_missing(self, db_path):
        from bybit_mcp_server.auth.db import load_oauth_client

        assert load_oauth_client(db_path, "nonexistent") is None


class TestOAuthAuthCodes:
    def test_save_and_load(self, db_path):
        from bybit_mcp_server.auth.db import load_auth_code, save_auth_code, save_oauth_client

        save_oauth_client(db_path, "c1", '{"client_id":"c1"}')
        save_auth_code(db_path, "code-1", "c1", '{"code":"code-1"}', time.time() + 300)
        row = load_auth_code(db_path, "code-1")
        assert row is not None
        assert row["code"] == "code-1"

    def test_expired_code_returns_none(self, db_path):
        from bybit_mcp_server.auth.db import load_auth_code, save_auth_code, save_oauth_client

        save_oauth_client(db_path, "c1", '{"client_id":"c1"}')
        save_auth_code(db_path, "expired", "c1", '{"code":"expired"}', time.time() - 1)
        assert load_auth_code(db_path, "expired") is None

    def test_delete(self, db_path):
        from bybit_mcp_server.auth.db import delete_auth_code, load_auth_code, save_auth_code, save_oauth_client

        save_oauth_client(db_path, "c1", '{"client_id":"c1"}')
        save_auth_code(db_path, "code-2", "c1", '{}', time.time() + 300)
        delete_auth_code(db_path, "code-2")
        assert load_auth_code(db_path, "code-2") is None


class TestOAuthTokens:
    def test_save_and_load(self, db_path):
        from bybit_mcp_server.auth.db import save_oauth_client, save_token, load_token

        save_oauth_client(db_path, "c1", '{"client_id":"c1"}')
        save_token(db_path, "access-1", "access", "c1", ["read"], expires_at=int(time.time()) + 3600)
        row = load_token(db_path, "access-1", "access")
        assert row is not None
        assert row["token"] == "access-1"

    def test_expired_token_returns_none(self, db_path):
        from bybit_mcp_server.auth.db import save_oauth_client, save_token, load_token

        save_oauth_client(db_path, "c1", '{"client_id":"c1"}')
        save_token(db_path, "expired", "access", "c1", ["read"], expires_at=int(time.time()) - 1)
        assert load_token(db_path, "expired", "access") is None

    def test_delete_with_related(self, db_path):
        from bybit_mcp_server.auth.db import delete_token, load_token, save_oauth_client, save_token

        save_oauth_client(db_path, "c1", '{"client_id":"c1"}')
        save_token(db_path, "access-2", "access", "c1", ["read"], related_token="refresh-2")
        save_token(db_path, "refresh-2", "refresh", "c1", ["read"], related_token="access-2")
        delete_token(db_path, "access-2")
        assert load_token(db_path, "access-2", "access") is None
        assert load_token(db_path, "refresh-2", "refresh") is None


class TestBybitCredentials:
    def test_save_and_load(self, db_path):
        from bybit_mcp_server.auth.db import load_bybit_credentials, save_bybit_credentials

        save_bybit_credentials(db_path, "key123", "secret456", testnet=True, mode="trade", recv_window=5000)
        creds = load_bybit_credentials(db_path)
        assert creds is not None
        assert creds["api_key"] == "key123"
        assert creds["api_secret"] == "secret456"
        assert creds["testnet"] is True
        assert creds["mode"] == "trade"
        assert creds["recv_window"] == 5000

    def test_update_overwrites(self, db_path):
        from bybit_mcp_server.auth.db import load_bybit_credentials, save_bybit_credentials

        save_bybit_credentials(db_path, "key1", "secret1")
        save_bybit_credentials(db_path, "key2", "secret2", testnet=False, mode="full")
        creds = load_bybit_credentials(db_path)
        assert creds["api_key"] == "key2"
        assert creds["testnet"] is False
        assert creds["mode"] == "full"

    def test_load_empty(self, db_path):
        from bybit_mcp_server.auth.db import load_bybit_credentials

        assert load_bybit_credentials(db_path) is None

    def test_credentials_encrypted_at_rest(self, db_path):
        """Verify raw DB values are encrypted, not plaintext."""
        import sqlite3

        from bybit_mcp_server.auth.db import save_bybit_credentials

        save_bybit_credentials(db_path, "my-api-key", "my-api-secret")
        conn = sqlite3.connect(db_path)
        row = conn.execute("SELECT api_key_encrypted, api_secret_encrypted FROM bybit_credentials WHERE id = 1").fetchone()
        conn.close()
        assert b"my-api-key" not in row[0]
        assert b"my-api-secret" not in row[1]
