import sys
from pathlib import Path
from tempfile import TemporaryDirectory
from unittest.mock import patch
import json
import asyncio
import os
import base64

sys.path.append(str(Path(__file__).resolve().parents[1]))

from seedpass.core.encryption import EncryptionManager
from nostr.client import NostrClient
import nostr.client as nostr_client
import constants


def test_nostr_client_uses_custom_relays():
    with TemporaryDirectory() as tmpdir:
        key = base64.urlsafe_b64encode(os.urandom(32))
        enc_mgr = EncryptionManager(key, Path(tmpdir))
        custom_relays = ["wss://relay1", "wss://relay2"]

        with patch("nostr.client.ClientBuilder") as MockBuilder, patch(
            "nostr.client.KeyManager"
        ), patch.object(NostrClient, "initialize_client_pool"):
            mock_builder = MockBuilder.return_value
            with patch.object(enc_mgr, "decrypt_parent_seed", return_value="seed"):
                client = NostrClient(enc_mgr, "fp", relays=custom_relays)

        assert client.relays == custom_relays


class FakeAddRelaysClient:
    def __init__(self, _signer):
        self.added = []
        self.connected = False

    async def add_relays(self, relays):
        self.added.append(relays)

    async def connect(self):
        self.connected = True


class FakeAddRelayClient:
    def __init__(self, _signer):
        self.added = []
        self.connected = False

    async def add_relay(self, relay):
        self.added.append(relay)

    async def connect(self):
        self.connected = True


class FakeWebSocket:
    def __init__(self, messages):
        self.messages = messages

    async def __aenter__(self):
        return self

    async def __aexit__(self, exc_type, exc, tb):
        pass

    async def send(self, _):
        pass

    async def recv(self):
        if self.messages:
            return self.messages.pop(0)
        await asyncio.sleep(0)


def _setup_client(tmpdir, fake_cls):
    key = base64.urlsafe_b64encode(os.urandom(32))
    enc_mgr = EncryptionManager(key, Path(tmpdir))

    with patch("nostr.client.Client", fake_cls), patch(
        "nostr.client.KeyManager"
    ) as MockKM, patch.object(enc_mgr, "decrypt_parent_seed", return_value="seed"):
        km_inst = MockKM.return_value
        km_inst.keys.private_key_hex.return_value = "1" * 64
        client = NostrClient(enc_mgr, "fp")
    return client


def test_initialize_client_pool_add_relays_used(tmp_path):
    client = _setup_client(tmp_path, FakeAddRelaysClient)
    fc = client.client
    client.connect()
    assert [[str(r) for r in relays] for relays in fc.added] == [client.relays]
    assert fc.connected is True


def test_initialize_client_pool_add_relay_fallback(tmp_path):
    client = _setup_client(tmp_path, FakeAddRelayClient)
    fc = client.client
    client.connect()
    assert [str(r) for r in fc.added] == client.relays
    assert fc.connected is True


def test_check_relay_health_runs_async(tmp_path, monkeypatch):
    client = _setup_client(tmp_path, FakeAddRelayClient)

    recorded = {}

    async def fake_check(min_relays, timeout):
        recorded["args"] = (min_relays, timeout)
        return 1

    monkeypatch.setattr(client, "_check_relay_health", fake_check)
    result = client.check_relay_health(3, timeout=2)

    assert result == 1
    assert recorded["args"] == (3, 2)


def test_ping_relay_accepts_eose(tmp_path, monkeypatch):
    client = _setup_client(tmp_path, FakeAddRelayClient)

    fake_ws = FakeWebSocket([json.dumps(["EOSE"])])

    def fake_connect(*_args, **_kwargs):
        return fake_ws

    monkeypatch.setattr(nostr_client.websockets, "connect", fake_connect)

    result = asyncio.run(client._ping_relay("wss://relay", timeout=0.1))

    assert result is True


def test_update_relays_reinitializes_pool(tmp_path, monkeypatch):
    client = _setup_client(tmp_path, FakeAddRelayClient)

    monkeypatch.setattr(nostr_client, "Client", FakeAddRelaysClient)

    called = {"ran": False}

    def fake_init(self):
        called["ran"] = True

    monkeypatch.setattr(NostrClient, "initialize_client_pool", fake_init)

    new_relays = ["wss://relay1"]
    client.update_relays(new_relays)

    assert called["ran"] is True
    assert isinstance(client.client, FakeAddRelaysClient)
    assert client.relays == new_relays


def test_retrieve_json_sync_backoff(tmp_path, monkeypatch):
    client = _setup_client(tmp_path, FakeAddRelayClient)

    monkeypatch.setattr("nostr.client.MAX_RETRIES", 3)
    monkeypatch.setattr("nostr.client.RETRY_DELAY", 1)
    monkeypatch.setattr("constants.MAX_RETRIES", 3)
    monkeypatch.setattr("constants.RETRY_DELAY", 1)
    monkeypatch.setattr("seedpass.core.config_manager.MAX_RETRIES", 3)
    monkeypatch.setattr("seedpass.core.config_manager.RETRY_DELAY", 1)

    sleeps: list[float] = []

    def fake_sleep(d):
        sleeps.append(d)

    monkeypatch.setattr(nostr_client.time, "sleep", fake_sleep)

    async def fake_async(self):
        return None

    monkeypatch.setattr(NostrClient, "_retrieve_json_from_nostr", fake_async)

    result = client.retrieve_json_from_nostr_sync()

    assert result is None
    assert sleeps == [1, 2]
