from __future__ import annotations

from pathlib import Path

import pytest
from conftest import make_runtime
from fastapi.testclient import TestClient

from scopewrit.api import api_key_sha256, create_app
from scopewrit.errors import GatewayError
from scopewrit.mcp import GuardedMCPFirewall
from scopewrit.models import ActionProposal, Decision, Effect, ScopeRule


def test_mcp_firewall_executes_only_an_allowed_tool(tmp_path: Path) -> None:
    runtime = make_runtime(
        tmp_path / "runtime",
        rules=(
            ScopeRule(
                rule_id="allow-read-record",
                effect=Effect.ALLOW,
                action="mcp.call",
                resource="mcp://records/read_record",
                max_uses=1,
            ),
        ),
    )
    observed = []
    allowed = GuardedMCPFirewall(runtime.engine).call(
        attestation=runtime.attestation,
        run_id=runtime.manifest.run_id,
        request_id="mcp-allowed",
        server_id="records",
        tool_name="read_record",
        arguments={"record_id": "record-7"},
        operation=lambda arguments: observed.append(arguments) or {"ok": True},
    )
    assert allowed.value == {"ok": True}
    assert observed == [{"record_id": "record-7"}]

    with pytest.raises(GatewayError) as denied:
        GuardedMCPFirewall(runtime.engine).call(
            attestation=runtime.attestation,
            run_id=runtime.manifest.run_id,
            request_id="mcp-denied",
            server_id="records",
            tool_name="delete_record",
            arguments={"record_id": "record-7"},
            operation=lambda _: observed.append("should-not-run"),
        )
    assert denied.value.decision.decision == Decision.DENY
    assert "should-not-run" not in observed


def test_control_api_requires_key_and_keeps_egress_off(runtime) -> None:
    api_key = "test-key-with-at-least-24-characters"
    client = TestClient(
        create_app(
            runtime.engine,
            expected_api_key_sha256=api_key_sha256(api_key),
            enable_http_egress=False,
        )
    )
    health = client.get("/api/health")
    assert health.status_code == 200
    assert health.json()["http_egress_enabled"] is False

    action = ActionProposal(
        request_id="api-authorize",
        run_id=runtime.manifest.run_id,
        action="network.http",
        resource="https://example.com/authorized/value",
        method="GET",
    )
    body = {
        "attestation": runtime.attestation,
        "proposal": action.model_dump(mode="json"),
    }
    assert client.post("/v1/authorize", json=body).status_code == 401
    response = client.post(
        "/v1/authorize",
        json=body,
        headers={"X-ScopeWrit-Key": api_key},
    )
    assert response.status_code == 200
    assert response.json()["decision"] == "ALLOW"

    egress = client.post(
        "/v1/egress/http",
        json={
            "attestation": runtime.attestation,
            "run_id": runtime.manifest.run_id,
            "request_id": "api-egress-disabled",
            "method": "GET",
            "url": "https://example.com/authorized/value",
        },
        headers={"X-ScopeWrit-Key": api_key},
    )
    assert egress.status_code == 403
    assert "disabled" in egress.json()["detail"]


def test_run_id_cannot_escape_runtime_directory(runtime) -> None:
    client = TestClient(
        create_app(
            runtime.engine,
            expected_api_key_sha256=api_key_sha256("test-key-with-at-least-24-characters"),
        )
    )
    response = client.post(
        "/v1/runs/run-../../outside/close",
        headers={"X-ScopeWrit-Key": "test-key-with-at-least-24-characters"},
    )
    assert response.status_code in {403, 404}


def test_closed_audit_can_be_read_back_without_reclosing(runtime) -> None:
    key = "test-key-with-at-least-24-characters"
    client = TestClient(create_app(runtime.engine, expected_api_key_sha256=api_key_sha256(key)))
    path = f"/v1/runs/{runtime.manifest.run_id}/audit"
    assert client.get(path).status_code == 401
    active = client.get(path, headers={"X-ScopeWrit-Key": key})
    assert active.status_code == 403
    assert "not been finalized" in active.json()["detail"]
    packet = runtime.engine.close_run(runtime.manifest.run_id)
    response = client.get(path, headers={"X-ScopeWrit-Key": key})
    assert response.status_code == 200
    assert response.json() == packet.model_dump(mode="json")
