from __future__ import annotations

import json
from pathlib import Path

import httpx
import pytest

from scopewrit.benchmark import run_benchmark
from scopewrit.errors import GatewayError, LedgerError
from scopewrit.gateway import GuardedExecutor, GuardedHTTPClient
from scopewrit.ledger import verify_audit_packet, verify_ledger
from scopewrit.models import ActionProposal, Decision


def test_denied_adapter_never_calls_operation(runtime) -> None:
    called = False

    def operation() -> str:
        nonlocal called
        called = True
        return "should-not-run"

    proposal = ActionProposal(
        request_id="denied-operation",
        run_id=runtime.manifest.run_id,
        action="network.http",
        resource="https://outside.example/value",
        method="GET",
    )
    with pytest.raises(GatewayError) as captured:
        GuardedExecutor(runtime.engine, adapter="http").execute(
            attestation=runtime.attestation,
            proposal=proposal,
            operation=operation,
        )
    assert captured.value.decision.decision == Decision.DENY
    assert called is False


def test_audit_packet_and_ledger_bind_then_detect_tamper(runtime) -> None:
    proposal = ActionProposal(
        request_id="audited-operation",
        run_id=runtime.manifest.run_id,
        action="network.http",
        resource="https://example.com/authorized/value",
        method="GET",
    )
    result = GuardedExecutor(runtime.engine, adapter="http").execute(
        attestation=runtime.attestation,
        proposal=proposal,
        operation=lambda: "completed",
        outcome=lambda value: {"status": value},
    )
    assert result.value == "completed"
    packet = runtime.engine.close_run(runtime.manifest.run_id)
    ledger_path = (
        runtime.engine.data_dir / "runs" / runtime.manifest.run_id / "flight-recorder.jsonl"
    )
    status = verify_ledger(ledger_path)
    verified = verify_audit_packet(
        packet,
        runtime.engine.issuer_trust,
        ledger_path,
    )
    assert verified.ledger_head_sha256 == status["head_sha256"]

    lines = ledger_path.read_text(encoding="utf-8").splitlines()
    record = json.loads(lines[1])
    record["payload"]["action"] = "tampered.action"
    lines[1] = json.dumps(record, separators=(",", ":"), sort_keys=True)
    ledger_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
    with pytest.raises(LedgerError, match="digest mismatch"):
        verify_ledger(ledger_path)


def test_isolated_guarded_vs_unguarded_benchmark(tmp_path: Path) -> None:
    report = run_benchmark(tmp_path / "benchmark")
    assert report["verdict"] == "PASS"
    assert all(report["invariants"].values())
    assert report["unguarded"]["synthetic_compromise"] is True
    assert report["guarded"]["synthetic_compromise"] is False
    assert report["guarded"]["outside_hits"] == 0


def test_http_adapter_rejects_host_header_before_authorization(runtime) -> None:
    client = GuardedHTTPClient(runtime.engine)
    with pytest.raises(GatewayError, match="header"):
        client.request(
            attestation=runtime.attestation,
            run_id=runtime.manifest.run_id,
            request_id="host-header-confusion",
            method="GET",
            url="https://example.com/authorized/value",
            headers={"Host": "outside.example"},
        )
    client.close()


def test_http_adapter_bounds_request_and_response_bodies(runtime) -> None:
    transport = httpx.MockTransport(
        lambda request: httpx.Response(200, content=b"response-too-large")
    )
    client = GuardedHTTPClient(
        runtime.engine,
        transport=transport,
        max_request_bytes=4,
        max_response_bytes=4,
    )
    client.resolve = lambda _: ("93.184.216.34",)
    with pytest.raises(GatewayError, match="request body"):
        client.request(
            attestation=runtime.attestation,
            run_id=runtime.manifest.run_id,
            request_id="request-body-limit",
            method="GET",
            url="https://example.com/authorized/value",
            content=b"12345",
        )
    with pytest.raises(GatewayError, match="response body"):
        client.request(
            attestation=runtime.attestation,
            run_id=runtime.manifest.run_id,
            request_id="response-body-limit",
            method="GET",
            url="https://example.com/authorized/value",
        )
    client.close()
