"""US-033: leader-only settlement loop with batched USDT payouts.

Mining-pool trigger (threshold OR max-period, dust floor), pending debited
before the transaction is sent, unconfirmed batches resent by settlement id
(never double-paying), banned wallets skipped, history queryable over HTTP.
"""

import time

import pytest

from meshnet_contracts import LocalSolanaContracts
from meshnet_tracker.billing import (
    BillingLedger,
    DEFAULT_PAYOUT_DUST_FLOOR,
    DEFAULT_PAYOUT_THRESHOLD,
    DEFAULT_SETTLE_PERIOD,
)
from meshnet_tracker.server import TrackerServer

MODEL = "stub-model"


class _FakePayoutTreasury:
    """Records payout batches; can be told to fail the next N sends."""

    def __init__(self):
        self.batches: list[list[tuple[str, float]]] = []
        self.fail_next = 0
        self._counter = 0

    def list_new_deposits(self, is_seen, *, limit=200):
        return []

    def send_payouts(self, payouts):
        if self.fail_next > 0:
            self.fail_next -= 1
            raise RuntimeError("rpc timeout (simulated)")
        self.batches.append(list(payouts))
        self._counter += 1
        return f"fake-tx-{self._counter}"


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


def _make_tracker(ledger, treasury, *, threshold=0.01, period=3600.0,
                  dust=0.001, contracts=None, cluster_peers=None):
    return TrackerServer(
        billing=ledger,
        treasury=treasury,
        contracts=contracts,
        deposit_poll_interval=30.0,
        settle_period=period,
        payout_threshold=threshold,
        payout_dust_floor=dust,
        settlement_check_interval=0.05,
        cluster_peers=cluster_peers,
    )


def test_threshold_triggers_payout_and_zeroes_pending():
    "Threshold triggers payout and zeroes pending\n\nTags: billing, settlement"
    ledger = BillingLedger(starting_credit=10.0, default_price_per_1k=0.02)
    ledger.charge_request("client", MODEL, 1000, [("wallet-a", 12)])  # 0.018 pending
    treasury = _FakePayoutTreasury()
    tracker = _make_tracker(ledger, treasury, threshold=0.01)
    tracker.start()
    try:
        assert _wait_for(lambda: treasury.batches)
        assert treasury.batches[0] == [("wallet-a", pytest.approx(0.018))]
        assert ledger.get_node_pending("wallet-a") == pytest.approx(0.0)

        # /v1/billing/settlements is admin-gated now (ADR-0017, covered in
        # test_auth_boundary.py); verify content via the ledger directly.
        history = ledger.settlement_history()
        assert len(history) == 1
        assert history[0]["signature"] == "fake-tx-1"
        assert history[0]["payouts"] == [
            {"wallet": "wallet-a", "amount": pytest.approx(0.018)}
        ]
    finally:
        tracker.stop()


def test_period_triggers_payout_below_threshold():
    "Period triggers payout below threshold\n\nTags: billing, settlement"
    ledger = BillingLedger(starting_credit=10.0, default_price_per_1k=0.02)
    ledger.charge_request("client", MODEL, 1000, [("wallet-a", 12)])
    treasury = _FakePayoutTreasury()
    tracker = _make_tracker(ledger, treasury, threshold=100.0, period=0.2)
    tracker.start()
    try:
        assert _wait_for(lambda: treasury.batches)
        assert ledger.get_node_pending("wallet-a") == pytest.approx(0.0)
    finally:
        tracker.stop()


def test_dust_floor_blocks_tiny_payouts():
    "Dust floor blocks tiny payouts\n\nTags: billing, settlement"
    ledger = BillingLedger(starting_credit=10.0, default_price_per_1k=0.02)
    ledger.charge_request("client", MODEL, 10, [("wallet-a", 12)])  # 0.00018 pending
    treasury = _FakePayoutTreasury()
    tracker = _make_tracker(ledger, treasury, threshold=0.0, period=0.1, dust=0.001)
    tracker.start()
    try:
        time.sleep(0.5)
        assert treasury.batches == []
        assert ledger.get_node_pending("wallet-a") > 0
    finally:
        tracker.stop()


def test_failed_transaction_retries_without_double_pay():
    "Failed transaction retries without double pay\n\nTags: billing, settlement"
    ledger = BillingLedger(starting_credit=10.0, default_price_per_1k=0.02)
    ledger.charge_request("client", MODEL, 1000, [("wallet-a", 12)])
    treasury = _FakePayoutTreasury()
    treasury.fail_next = 3
    tracker = _make_tracker(ledger, treasury, threshold=0.01)
    tracker.start()
    try:
        assert _wait_for(lambda: treasury.batches)
        time.sleep(0.3)  # give the loop room to (incorrectly) double-send
        assert len(treasury.batches) == 1
        assert treasury.batches[0] == [("wallet-a", pytest.approx(0.018))]
        assert ledger.get_node_pending("wallet-a") == pytest.approx(0.0)
        history = ledger.settlement_history()
        assert len(history) == 1  # one settlement id across all retries
        assert history[0]["signature"] == "fake-tx-1"
    finally:
        tracker.stop()


def test_banned_wallet_is_never_paid():
    "Banned wallet is never paid\n\nTags: billing, security, settlement, wallet"
    contracts = LocalSolanaContracts()
    contracts.registry.ban_wallet("wallet-banned")
    ledger = BillingLedger(starting_credit=10.0, default_price_per_1k=0.02)
    ledger.charge_request("client", MODEL, 1000, [("wallet-banned", 6), ("wallet-ok", 6)])
    treasury = _FakePayoutTreasury()
    tracker = _make_tracker(ledger, treasury, threshold=0.001, contracts=contracts)
    tracker.start()
    try:
        assert _wait_for(lambda: treasury.batches)
        time.sleep(0.3)
        paid_wallets = {w for batch in treasury.batches for w, _ in batch}
        assert paid_wallets == {"wallet-ok"}
        assert ledger.get_node_pending("wallet-banned") > 0  # held, never paid
    finally:
        tracker.stop()


def test_non_leader_never_signs():
    "A tracker in a cluster whose peers are unreachable never wins an election, so it must never send a payout transaction.\n\nTags: billing, settlement"

    ledger = BillingLedger(starting_credit=10.0, default_price_per_1k=0.02)
    ledger.charge_request("client", MODEL, 1000, [("wallet-a", 12)])
    treasury = _FakePayoutTreasury()
    tracker = _make_tracker(
        ledger, treasury, threshold=0.001,
        cluster_peers=["http://127.0.0.1:1", "http://127.0.0.1:2"],
    )
    tracker.start()
    try:
        time.sleep(0.8)
        assert treasury.batches == []
        assert ledger.get_node_pending("wallet-a") > 0
    finally:
        tracker.stop()


# Production defaults — sourced from billing.py (cli.py uses the same constants)
PROD_SETTLE_PERIOD = DEFAULT_SETTLE_PERIOD
PROD_PAYOUT_THRESHOLD = DEFAULT_PAYOUT_THRESHOLD
PROD_DUST_FLOOR = DEFAULT_PAYOUT_DUST_FLOOR


def _set_pending_since(ledger: BillingLedger, wallet: str, ts: float) -> None:
    with ledger._lock:
        ledger._pending_since[wallet] = ts


def test_prod_settlement_constants():
    "Production settlement policy is 24 h period, 5 USDT threshold, 0.01 dust floor.\n\nTags: billing, settlement"
    assert DEFAULT_SETTLE_PERIOD == 86400.0
    assert DEFAULT_PAYOUT_THRESHOLD == 5.0
    assert DEFAULT_PAYOUT_DUST_FLOOR == 0.01


def test_payables_24h_period_not_due_before_elapsed():
    "Sub-threshold pending is not payable until the 24 h period elapses.\n\nTags: billing, settlement"
    ledger = BillingLedger(starting_credit=100.0, default_price_per_1k=0.02)
    ledger.charge_request("client", MODEL, 1000, [("node-wallet", 12)])
    assert ledger.get_node_pending("node-wallet") == pytest.approx(0.018)

    t0 = 1_700_000_000.0
    _set_pending_since(ledger, "node-wallet", t0)
    due_early = ledger.payables(
        threshold=PROD_PAYOUT_THRESHOLD,
        max_period=PROD_SETTLE_PERIOD,
        dust_floor=PROD_DUST_FLOOR,
        now=t0 + PROD_SETTLE_PERIOD - 1.0,
    )
    assert due_early == []


def test_payables_24h_period_triggers_at_boundary():
    "Sub-threshold pending becomes payable exactly after the 24 h period.\n\nTags: billing, settlement"
    ledger = BillingLedger(starting_credit=100.0, default_price_per_1k=0.02)
    ledger.charge_request("client", MODEL, 1000, [("node-wallet", 12)])
    pending = ledger.get_node_pending("node-wallet")

    t0 = 1_700_000_000.0
    _set_pending_since(ledger, "node-wallet", t0)
    due = ledger.payables(
        threshold=PROD_PAYOUT_THRESHOLD,
        max_period=PROD_SETTLE_PERIOD,
        dust_floor=PROD_DUST_FLOOR,
        now=t0 + PROD_SETTLE_PERIOD,
    )
    assert due == [("node-wallet", pytest.approx(pending))]


def test_payables_threshold_triggers_before_24h_period():
    "Pending ≥ 5 USDT pays immediately without waiting the 24 h period.\n\nTags: billing, settlement"
    ledger = BillingLedger(starting_credit=1000.0, default_price_per_1k=0.02)
    # cost = 300k * 0.02 / 1k = 6.0 USDT → node share 5.4 USDT
    ledger.charge_request("client", MODEL, 300_000, [("node-wallet", 12)])
    assert ledger.get_node_pending("node-wallet") >= PROD_PAYOUT_THRESHOLD

    due = ledger.payables(
        threshold=PROD_PAYOUT_THRESHOLD,
        max_period=PROD_SETTLE_PERIOD,
        dust_floor=PROD_DUST_FLOOR,
        now=time.time(),
    )
    assert len(due) == 1
    assert due[0][0] == "node-wallet"


def test_prod_defaults_hold_sub_threshold_pending():
    "Tracker with prod 24 h / 5 USDT settings does not pay tiny balances early.\n\nTags: billing, settlement"
    ledger = BillingLedger(starting_credit=100.0, default_price_per_1k=0.02)
    ledger.charge_request("client", MODEL, 1000, [("node-wallet", 12)])
    treasury = _FakePayoutTreasury()
    tracker = _make_tracker(
        ledger,
        treasury,
        threshold=PROD_PAYOUT_THRESHOLD,
        period=PROD_SETTLE_PERIOD,
        dust=PROD_DUST_FLOOR,
    )
    tracker.start()
    try:
        time.sleep(0.5)
        assert treasury.batches == []
        assert ledger.get_node_pending("node-wallet") > 0
    finally:
        tracker.stop()


def test_prod_threshold_triggers_immediate_payout():
    "Tracker with prod 24 h period still pays when pending ≥ 5 USDT.\n\nTags: billing, settlement"
    ledger = BillingLedger(starting_credit=1000.0, default_price_per_1k=0.02)
    ledger.charge_request("client", MODEL, 300_000, [("node-wallet", 12)])
    treasury = _FakePayoutTreasury()
    tracker = _make_tracker(
        ledger,
        treasury,
        threshold=PROD_PAYOUT_THRESHOLD,
        period=PROD_SETTLE_PERIOD,
        dust=PROD_DUST_FLOOR,
    )
    tracker.start()
    try:
        assert _wait_for(lambda: treasury.batches)
        assert ledger.get_node_pending("node-wallet") == pytest.approx(0.0)
        assert treasury.batches[0][0][0] == "node-wallet"
        assert treasury.batches[0][0][1] >= PROD_PAYOUT_THRESHOLD
    finally:
        tracker.stop()
