"""US-032: devnet custodial treasury — wallet binding + deposit watcher.

The deposit watcher polls the treasury token account and credits the sending
wallet's bound API key exactly once per transaction signature. The watcher is
exercised against a fake treasury (hermetic CI, ADR-0007); the real
solana-test-validator flow runs only when the binary is installed.
"""

import json
import shutil
import time
import urllib.error
import urllib.request

import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey

from meshnet_tracker.billing import BillingLedger
from meshnet_tracker.server import TrackerServer
from meshnet_tracker.wallet_proof import binding_message


def _keypair():
    """Deterministic-enough local ed25519 keypair + base58 address for tests."""
    priv = Ed25519PrivateKey.generate()
    from meshnet_node.wallet import _b58encode

    public = priv.public_key().public_bytes_raw()
    return priv, _b58encode(public)


def _sign(priv: Ed25519PrivateKey, api_key: str, wallet: str) -> str:
    return priv.sign(binding_message(api_key, wallet)).hex()


class _FakeDeposit:
    def __init__(self, signature, sender_wallet, amount_usdt):
        self.signature = signature
        self.sender_wallet = sender_wallet
        self.amount_usdt = amount_usdt


class _FakeTreasury:
    """Stands in for SolanaCustodialTreasury: a queue of confirmed deposits."""

    def __init__(self):
        self.deposits: list[_FakeDeposit] = []

    def list_new_deposits(self, is_seen, *, limit=200):
        return [d for d in self.deposits if not is_seen(d.signature)]


def _post_json(url: str, payload: dict, headers: dict | None = None) -> dict:
    req = urllib.request.Request(
        url,
        data=json.dumps(payload).encode(),
        headers={"Content-Type": "application/json", **(headers or {})},
        method="POST",
    )
    with urllib.request.urlopen(req) as r:
        return json.loads(r.read())


@pytest.fixture
def watched_tracker():
    ledger = BillingLedger(starting_credit=0.0, default_price_per_1k=0.02)
    treasury = _FakeTreasury()
    tracker = TrackerServer(
        billing=ledger,
        treasury=treasury,
        deposit_poll_interval=0.1,
    )
    port = tracker.start()
    yield f"http://127.0.0.1:{port}", ledger, treasury
    tracker.stop()


def _wait_for(predicate, timeout=3.0):
    deadline = time.monotonic() + timeout
    while time.monotonic() < deadline:
        if predicate():
            return True
        time.sleep(0.05)
    return False


def test_wallet_register_requires_auth(watched_tracker):
    "Wallet register requires auth\n\nTags: auth, security, wallet"
    tracker_url, _, _ = watched_tracker
    with pytest.raises(urllib.error.HTTPError) as exc_info:
        _post_json(f"{tracker_url}/v1/wallet/register", {"wallet": "So1anaWa11et111"})
    assert exc_info.value.code == 401


def test_deposit_credits_bound_api_key_exactly_once(watched_tracker):
    "Deposit credits bound api key exactly once\n\nTags: general"
    tracker_url, ledger, treasury = watched_tracker
    priv, wallet = _keypair()
    reply = _post_json(
        f"{tracker_url}/v1/wallet/register",
        {"wallet": wallet, "signature": _sign(priv, "client-key-1", wallet)},
        headers={"Authorization": "Bearer client-key-1"},
    )
    assert reply["bound"] is True

    treasury.deposits.append(_FakeDeposit("sig-1", wallet, 25.0))
    assert _wait_for(lambda: ledger.get_client_balance("client-key-1") > 0)
    assert ledger.get_client_balance("client-key-1") == pytest.approx(25.0)

    # the same signature keeps being reported by the RPC — credited once only
    time.sleep(0.4)
    assert ledger.get_client_balance("client-key-1") == pytest.approx(25.0)

    # replay through the ledger API directly is also a no-op
    assert ledger.credit_deposit("client-key-1", 25.0, "sig-1") is None


def test_unbound_wallet_deposit_is_not_credited(watched_tracker):
    "Unbound wallet deposit is not credited\n\nTags: security, wallet"
    _, ledger, treasury = watched_tracker
    treasury.deposits.append(_FakeDeposit("sig-2", "UnknownWallet999", 10.0))
    time.sleep(0.4)
    assert ledger.snapshot()["clients"] == {}


def test_binding_replicates_via_events():
    "Binding replicates via events\n\nTags: general"
    a = BillingLedger(starting_credit=0.0)
    b = BillingLedger(starting_credit=0.0)
    a.bind_wallet("key-9", "WalletNine")
    a.credit_deposit("key-9", 3.0, "sig-9")
    events, _ = a.events_since(0)
    b.apply_events(events)
    assert b.api_key_for_wallet("WalletNine") == "key-9"
    assert b.get_client_balance("key-9") == pytest.approx(3.0)
    # deposit event id embeds the signature → replicated replay is a no-op
    assert b.apply_events(events) == 0


def test_solana_adapter_derives_treasury_accounts():
    "Adapter smoke test without any RPC round-trip.\n\nTags: general"
    pytest.importorskip("solders")
    token_instructions = pytest.importorskip("spl.token.instructions")
    if not hasattr(token_instructions, "InitializeMintParams"):
        pytest.skip("installed spl.token lacks InitializeMintParams")
    from solders.keypair import Keypair

    from meshnet_contracts.solana_adapter import SolanaCustodialTreasury

    keypair = Keypair()
    treasury = SolanaCustodialTreasury(
        "http://127.0.0.1:8899",
        "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",  # real USDT mint address shape
        keypair,
    )
    assert treasury.treasury_wallet == str(keypair.pubkey())
    assert treasury.treasury_token_account  # deterministic ATA derivation


@pytest.mark.skipif(
    shutil.which("solana-test-validator") is None,
    reason="solana-test-validator not installed",
)
def test_mint_deposit_credit_flow_against_local_validator(tmp_path):
    "Full mint → deposit → credit flow on a local validator (US-032).\n\nTags: general"
    import subprocess

    from solders.keypair import Keypair

    from meshnet_contracts.solana_adapter import RpcClient, SolanaCustodialTreasury

    rpc_url = "http://127.0.0.1:8899"
    ledger_dir = tmp_path / "ledger"
    validator = subprocess.Popen(
        ["solana-test-validator", "--ledger", str(ledger_dir), "--quiet",
         "--rpc-port", "8899", "--reset"],
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
    )
    try:
        assert _wait_for(lambda: _rpc_alive(RpcClient(rpc_url)), timeout=60)

        treasury_kp, client_kp = Keypair(), Keypair()
        bootstrap = SolanaCustodialTreasury(
            rpc_url, "11111111111111111111111111111111", treasury_kp,
        )
        client_bootstrap = SolanaCustodialTreasury(
            rpc_url, "11111111111111111111111111111111", client_kp,
        )
        bootstrap.request_airdrop(2.0)
        client_bootstrap.request_airdrop(2.0)
        assert _wait_for(lambda: bootstrap.get_sol_balance() > 0, timeout=30)
        assert _wait_for(lambda: client_bootstrap.get_sol_balance() > 0, timeout=30)

        treasury, mint = bootstrap.create_mock_usdt_mint()
        assert _wait_for(
            lambda: treasury._account_exists(  # noqa: SLF001 — test-only poke
                __import__("solders.pubkey", fromlist=["Pubkey"]).Pubkey.from_string(mint)
            ),
            timeout=30,
        )
        treasury.ensure_token_account(treasury.treasury_wallet)
        treasury.mint_mock_usdt(str(client_kp.pubkey()), 50.0)

        # the client depositing into the treasury is just a transfer the
        # client signs — reuse send_payouts from an adapter bound to their key
        client_side = SolanaCustodialTreasury(rpc_url, mint, client_kp)
        assert _wait_for(
            lambda: _try_send(client_side, treasury.treasury_wallet, 25.0), timeout=30,
        )

        ledger = BillingLedger(starting_credit=0.0)
        ledger.bind_wallet("api-key-e2e", str(client_kp.pubkey()))

        def _credited():
            for dep in treasury.list_new_deposits(
                lambda sig: ledger.has_event(f"deposit-{sig}")
            ):
                key = ledger.api_key_for_wallet(dep.sender_wallet)
                if key:
                    ledger.credit_deposit(key, dep.amount_usdt, dep.signature)
            return ledger.get_client_balance("api-key-e2e") > 0

        assert _wait_for(_credited, timeout=60)
        assert ledger.get_client_balance("api-key-e2e") == pytest.approx(25.0)
    finally:
        validator.terminate()
        validator.wait(timeout=10)


def _try_send(adapter, wallet: str, amount: float) -> bool:
    try:
        adapter.send_payouts([(wallet, amount)])
        return True
    except Exception:
        return False


def _rpc_alive(rpc) -> bool:
    try:
        return rpc.call("getHealth") == "ok"
    except Exception:
        return False
