"""Alpha issues 01/02/20 (ADR-0017): unified auth boundary.

Forfeit accepts only the validator service token or an admin session; client
API keys and garbage bearers are rejected. Financial reads require an admin
session. Hive gossip mutations require a fresh HMAC signature — unsigned
requests apply nothing.
"""

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

import pytest

from meshnet_tracker.accounts import AccountStore
from meshnet_tracker.auth import sign_hive_request, verify_hive_request
from meshnet_tracker.billing import BillingLedger
from meshnet_tracker.server import TrackerServer

MODEL = "stub-model"
SERVICE_TOKEN = "svc-validator-secret-token"
HIVE_SECRET = "hive-shared-secret"


def _request(url, *, method="GET", payload=None, token=None, extra_headers=None):
    headers = {"Content-Type": "application/json", **(extra_headers or {})}
    if token:
        headers["Authorization"] = f"Bearer {token}"
    req = urllib.request.Request(
        url,
        data=json.dumps(payload).encode() if payload is not None else None,
        headers=headers,
        method=method,
    )
    with urllib.request.urlopen(req) as r:
        return json.loads(r.read())


def _status_of(callable_):
    try:
        callable_()
    except urllib.error.HTTPError as exc:
        return exc.code
    return 200


@pytest.fixture
def secured_tracker():
    accounts = AccountStore()
    admin = accounts.register(email="admin@example.com", password="admin-pass-123")
    user = accounts.register(email="user@example.com", password="user-pass-1234")
    admin_session = accounts.create_session(admin["account_id"])
    user_session = accounts.create_session(user["account_id"])
    client_api_key = accounts.create_api_key(user["account_id"])

    ledger = BillingLedger(starting_credit=1.0, default_price_per_1k=0.02)
    ledger.charge_request("client", MODEL, 1000, [("wallet-x", 12)])

    tracker = TrackerServer(
        billing=ledger,
        accounts=accounts,
        validator_service_token=SERVICE_TOKEN,
        hive_secret=HIVE_SECRET,
    )
    port = tracker.start()
    yield {
        "url": f"http://127.0.0.1:{port}",
        "ledger": ledger,
        "admin_session": admin_session,
        "user_session": user_session,
        "client_api_key": client_api_key,
    }
    tracker.stop()


# ---------------------------------------------------------------- forfeit


def test_forfeit_rejects_missing_garbage_and_api_key(secured_tracker):
    "Forfeit rejects missing garbage and api key\n\nTags: auth, http, security"

    url = f"{secured_tracker['url']}/v1/billing/forfeit"
    payload = {"wallet": "wallet-x", "reason": "fraud"}

    assert _status_of(lambda: _request(url, method="POST", payload=payload)) == 401
    assert _status_of(lambda: _request(url, method="POST", payload=payload, token="garbage")) == 403
    # a *valid* client API key must still be rejected on operator endpoints
    assert _status_of(lambda: _request(
        url, method="POST", payload=payload, token=secured_tracker["client_api_key"],
    )) == 403
    # nothing was forfeited by any of the rejected attempts
    assert secured_tracker["ledger"].get_node_pending("wallet-x") > 0


def test_forfeit_accepts_service_token_and_admin_session(secured_tracker):
    "Forfeit accepts service token and admin session\n\nTags: auth, http, security"

    url = f"{secured_tracker['url']}/v1/billing/forfeit"
    result = _request(
        url, method="POST", payload={"wallet": "wallet-x"}, token=SERVICE_TOKEN,
    )
    assert result["forfeited"] == pytest.approx(0.02 * 0.90)
    assert secured_tracker["ledger"].get_node_pending("wallet-x") == pytest.approx(0.0)

    # admin session is the operator override (idempotent second call: 0 pending)
    result = _request(
        url, method="POST", payload={"wallet": "wallet-x"},
        token=secured_tracker["admin_session"],
    )
    assert result["forfeited"] == pytest.approx(0.0)


# ------------------------------------------------------- financial reads


@pytest.mark.parametrize("path", [
    "/v1/billing/summary",
    "/v1/billing/settlements",
    "/v1/registry/wallets",
])
def test_financial_reads_require_admin_session(secured_tracker, path):
    "Financial reads require admin session\n\nTags: auth, http, security"

    url = f"{secured_tracker['url']}{path}"
    assert _status_of(lambda: _request(url)) == 401
    assert _status_of(lambda: _request(url, token=secured_tracker["user_session"])) == 403
    assert _status_of(lambda: _request(url, token=secured_tracker["client_api_key"])) == 403
    assert _status_of(lambda: _request(url, token=secured_tracker["admin_session"])) == 200


def test_benchmark_endpoints_require_admin_or_service(secured_tracker):
    "Benchmark endpoints require admin or service\n\nTags: auth, http, performance, security"

    url = f"{secured_tracker['url']}/v1/benchmark/results"
    assert _status_of(lambda: _request(url)) == 401
    assert _status_of(lambda: _request(url, token=secured_tracker["user_session"])) == 403
    assert _status_of(lambda: _request(url, token=SERVICE_TOKEN)) == 200
    assert _status_of(lambda: _request(url, token=secured_tracker["admin_session"])) == 200


def test_dashboard_stays_public(secured_tracker):
    "Dashboard stays public\n\nTags: auth, http, security"

    assert _status_of(lambda: urllib.request.urlopen(
        f"{secured_tracker['url']}/dashboard"
    )) == 200


# ---------------------------------------------------------------- gossip


def _gossip_events():
    peer = BillingLedger(starting_credit=0.0)
    peer.credit_client("injected-client", 999.0)
    events, _ = peer.events_since(0)
    return events


def test_unsigned_gossip_is_rejected_and_applies_nothing(secured_tracker):
    "Unsigned gossip is rejected and applies nothing\n\nTags: auth, gossip, http, network, security"

    url = f"{secured_tracker['url']}/v1/billing/gossip"
    payload = {"events": _gossip_events()}
    assert _status_of(lambda: _request(url, method="POST", payload=payload)) == 401
    # a bearer token is not a hive signature either
    assert _status_of(lambda: _request(
        url, method="POST", payload=payload, token=secured_tracker["admin_session"],
    )) == 401
    assert secured_tracker["ledger"].get_client_balance("injected-client") == 0.0


def test_signed_gossip_applies_and_wrong_secret_rejected(secured_tracker):
    "Signed gossip applies and wrong secret rejected\n\nTags: auth, gossip, http, network, security"

    url = f"{secured_tracker['url']}/v1/billing/gossip"
    body = json.dumps({"events": _gossip_events()}).encode()

    bad = sign_hive_request("not-the-hive-secret", body)
    assert _status_of(lambda: _request(
        url, method="POST", payload=json.loads(body), extra_headers=bad,
    )) == 401

    good = sign_hive_request(HIVE_SECRET, body)
    req = urllib.request.Request(
        url, data=body,
        headers={"Content-Type": "application/json", **good},
        method="POST",
    )
    with urllib.request.urlopen(req) as r:
        assert json.loads(r.read())["applied"] == 1
    assert secured_tracker["ledger"].get_client_balance("injected-client") == pytest.approx(999.0)


def test_stale_signature_rejected():
    "Stale signature rejected\n\nTags: auth, http, security"

    body = b'{"events": []}'
    stale = sign_hive_request(HIVE_SECRET, body, timestamp=time.time() - 3600)
    assert not verify_hive_request(HIVE_SECRET, stale, body)
    fresh = sign_hive_request(HIVE_SECRET, body)
    assert verify_hive_request(HIVE_SECRET, fresh, body)
    # signature must cover the body
    assert not verify_hive_request(HIVE_SECRET, fresh, b'{"events": [1]}')
    # fail closed without a configured secret
    assert not verify_hive_request(None, fresh, body)


def test_accounts_and_stats_gossip_also_gated(secured_tracker):
    "Accounts and stats gossip also gated\n\nTags: auth, gossip, http, network, security"

    for path in ("/v1/accounts/gossip", "/v1/stats/gossip"):
        url = f"{secured_tracker['url']}{path}"
        assert _status_of(lambda: _request(url, method="POST", payload={})) == 401


def test_push_to_peers_signs_so_peers_accept(secured_tracker):
    "Outgoing gossip from a tracker with the shared secret lands on a peer.\n\nTags: auth, http, security"

    sender = TrackerServer(
        billing=BillingLedger(starting_credit=0.0),
        cluster_peers=[secured_tracker["url"]],
        hive_secret=HIVE_SECRET,
    )
    body = json.dumps({"events": _gossip_events()}).encode()
    assert sender._push_to_peers("/v1/billing/gossip", body) is True
    assert secured_tracker["ledger"].get_client_balance("injected-client") == pytest.approx(999.0)
