"""Issue 08 (FRAUD: reputation model): scoring rules on top of the persisted
reputation/strike/ban fields from issue 05 (ADR-0018 §6).

Score derives only from tracker audit outcomes + uptime/latency: slow build,
instant loss, inactivity decay. Strikes apply a ×0.8 routing multiplier per
strike, separate from the (full) forfeiture penalty owned by issue 10.
"""

import json
import urllib.request

import pytest

from meshnet_contracts import (
    REPUTATION_CLEAN_AUDIT_DELTA,
    REPUTATION_FAILED_AUDIT_DELTA,
    LocalSolanaContracts,
)
from meshnet_node.server import StubNodeServer
from meshnet_validator import ValidatorProcess

MODEL = "stub-model"


# ---- documented deltas, applied via the persisted issue-05 fields ----


def test_clean_audit_increases_reputation_by_documented_delta():
    "Clean audit increases reputation by documented delta\n\nTags: general"
    contracts = LocalSolanaContracts()
    contracts.registry.record_audit_outcome("wallet-a", passed=False)  # 1.0 -> 0.7
    before = contracts.registry.get_wallet("wallet-a").reputation

    contracts.registry.record_audit_outcome("wallet-a", passed=True)

    after = contracts.registry.get_wallet("wallet-a").reputation
    assert after == pytest.approx(before + REPUTATION_CLEAN_AUDIT_DELTA)


def test_failed_audit_decreases_reputation_by_documented_delta():
    "Failed audit decreases reputation by documented delta\n\nTags: general"
    contracts = LocalSolanaContracts()
    contracts.registry.record_audit_outcome("wallet-b", passed=False)
    assert contracts.registry.get_wallet("wallet-b").reputation == pytest.approx(
        1.0 + REPUTATION_FAILED_AUDIT_DELTA
    )


def test_reputation_clamps_to_documented_bounds():
    "Reputation clamps to documented bounds\n\nTags: general"
    contracts = LocalSolanaContracts()
    for _ in range(10):
        contracts.registry.record_audit_outcome("wallet-c", passed=False)
    assert contracts.registry.get_wallet("wallet-c").reputation == 0.0

    for _ in range(30):
        contracts.registry.record_audit_outcome("wallet-c", passed=True)
    assert contracts.registry.get_wallet("wallet-c").reputation == 1.0


def test_reputation_uses_persisted_fields_not_a_second_schema(tmp_path):
    "Red (issue 08 test-first item 1): reputation deltas must persist and reload through the same event log issue 05 already wired — no parallel storage path.\n\nTags: persistence"

    db = str(tmp_path / "registry.sqlite")
    contracts = LocalSolanaContracts(registry_db=db)
    contracts.registry.record_audit_outcome("wallet-d", passed=False)
    contracts.save_to_db()

    reopened = LocalSolanaContracts(registry_db=db)
    wallet = reopened.registry.get_wallet("wallet-d")
    assert wallet.reputation == pytest.approx(1.0 + REPUTATION_FAILED_AUDIT_DELTA)
    assert wallet.strike_count == 0  # audit_outcome never strikes on its own


# ---- strike -> routing multiplier, separate from forfeiture ----


def test_strike_applies_graduated_routing_multiplier_not_full_penalty():
    "Strike applies graduated routing multiplier not full penalty\n\nTags: general"
    contracts = LocalSolanaContracts()
    contracts.registry.submit_stake("wallet-e", 500)
    assert contracts.registry.routing_multiplier("wallet-e") == pytest.approx(1.0)

    contracts.registry.record_strike("wallet-e", ban_threshold=10)
    assert contracts.registry.routing_multiplier("wallet-e") == pytest.approx(0.8)

    contracts.registry.record_strike("wallet-e", ban_threshold=10)
    assert contracts.registry.routing_multiplier("wallet-e") == pytest.approx(0.8 * 0.8)
    # stake (forfeiture surface) is untouched by strikes alone
    assert contracts.registry.get_wallet("wallet-e").stake_balance == 500


def test_three_strikes_still_bans_and_probation_still_enforced():
    "Three strikes still bans and probation still enforced\n\nTags: general"
    contracts = LocalSolanaContracts(probationary_job_count=2)
    for _ in range(3):
        contracts.registry.record_strike("wallet-f")
    wallet = contracts.registry.get_wallet("wallet-f")
    assert wallet.strike_count == 3
    assert wallet.banned is True

    contracts.registry.record_completed_job("wallet-g")
    assert contracts.registry.probationary_jobs_remaining("wallet-g") == 1


# ---- inactivity decay ----


def test_inactivity_decay_after_idle_days_without_completed_jobs():
    "Inactivity decay after idle days without completed jobs\n\nTags: general"
    contracts = LocalSolanaContracts()
    contracts.registry.record_completed_jobs("wallet-h", 1, ts=0.0)

    idle_seconds = 30 * 86400.0
    decayed = contracts.registry.apply_inactivity_decay(
        idle_seconds=idle_seconds, decay_amount=0.05, now=idle_seconds + 1.0,
    )

    assert "wallet-h" in decayed
    assert contracts.registry.get_wallet("wallet-h").reputation == pytest.approx(0.95)


def test_inactivity_decay_skips_wallets_active_within_the_window():
    "Inactivity decay skips wallets active within the window\n\nTags: security, wallet"
    contracts = LocalSolanaContracts()
    contracts.registry.record_completed_jobs("wallet-i", 1, ts=1000.0)

    decayed = contracts.registry.apply_inactivity_decay(
        idle_seconds=30 * 86400.0, now=1000.0 + 60.0,
    )

    assert decayed == {}
    assert contracts.registry.get_wallet("wallet-i").reputation == pytest.approx(1.0)


def test_inactivity_decay_applies_at_most_once_per_idle_window():
    "Inactivity decay applies at most once per idle window\n\nTags: general"
    contracts = LocalSolanaContracts()
    contracts.registry.record_completed_jobs("wallet-j", 1, ts=0.0)
    idle_seconds = 30 * 86400.0

    contracts.registry.apply_inactivity_decay(idle_seconds=idle_seconds, now=idle_seconds + 1.0)
    again = contracts.registry.apply_inactivity_decay(idle_seconds=idle_seconds, now=idle_seconds + 2.0)

    assert again == {}
    assert contracts.registry.get_wallet("wallet-j").reputation == pytest.approx(0.95)


def test_inactivity_decay_skips_banned_wallets():
    "Inactivity decay skips banned wallets\n\nTags: security, wallet"
    contracts = LocalSolanaContracts()
    contracts.registry.record_completed_jobs("wallet-k", 1, ts=0.0)
    contracts.registry.ban_wallet("wallet-k")

    decayed = contracts.registry.apply_inactivity_decay(now=30 * 86400.0 + 1.0)

    assert decayed == {}


# ---- no peer-to-peer reputation inputs ----


def test_registry_contract_has_no_peer_rating_api():
    "Registry contract has no peer rating api\n\nTags: general"
    contracts = LocalSolanaContracts()
    for forbidden in ("rate_peer", "submit_peer_rating", "peer_reputation"):
        assert not hasattr(contracts.registry, forbidden)


# ---- validator wiring: audit outcomes feed reputation end-to-end ----


def _record_event(contracts, session_id: str, output: str, wallet: str) -> None:
    contracts.validation.record_completed_inference(
        session_id=session_id,
        model=MODEL,
        messages=[{"role": "user", "content": "2+2"}],
        observed_output=output,
        route_nodes=[{"wallet_address": wallet, "shard_end": 31}],
    )


@pytest.fixture
def reference_node():
    node = StubNodeServer(shard_start=0, shard_end=31, is_last_shard=True)
    port = node.start()
    yield f"http://127.0.0.1:{port}"
    node.stop()


def _reference_output(reference_url: str) -> str:
    req = urllib.request.Request(
        f"{reference_url}/v1/infer",
        data=json.dumps({"messages": [{"role": "user", "content": "2+2"}]}).encode(),
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    with urllib.request.urlopen(req) as r:
        return json.loads(r.read())["text"]


def test_validator_credits_clean_audit_via_persisted_reputation(reference_node):
    "Validator credits clean audit via persisted reputation\n\nTags: persistence"
    contracts = LocalSolanaContracts()
    contracts.registry.record_audit_outcome("wallet-good", passed=False)  # 1.0 -> 0.7
    validator = ValidatorProcess(
        contracts=contracts, reference_node_url=reference_node, sample_rate=1.0, random_seed=7,
    )
    _record_event(contracts, "sess-clean", _reference_output(reference_node), "wallet-good")

    validator.validate_once()

    assert contracts.registry.get_wallet("wallet-good").reputation == pytest.approx(
        0.7 + REPUTATION_CLEAN_AUDIT_DELTA
    )


def test_validator_docks_reputation_on_failed_audit_without_double_striking(reference_node):
    "Validator docks reputation on failed audit without double striking\n\nTags: general"
    contracts = LocalSolanaContracts()
    contracts.registry.submit_stake("wallet-bad", 500)
    validator = ValidatorProcess(
        contracts=contracts,
        reference_node_url=reference_node,
        sample_rate=1.0,
        slash_amount=100,
        strike_threshold=3,
        random_seed=7,
    )
    _record_event(contracts, "sess-fraud", "fraudulent nonsense", "wallet-bad")

    validator.validate_once()

    wallet = contracts.registry.get_wallet("wallet-bad")
    assert wallet.reputation == pytest.approx(1.0 + REPUTATION_FAILED_AUDIT_DELTA)
    # exactly one strike from submit_slash_proof — record_audit_outcome must
    # not add a second
    assert wallet.strike_count == 1
