"""Workstream E4 transport: capability minting and the Cloud projection PUT (no network)."""

import json

import httpx
import pytest
from jose import jwt
from src.integrations.auth_projection_writer import (
    CORE_AUDIENCE,
    CORE_ISSUER,
    WRITE_CAPABILITY,
    CloudAuthProjectionWriter,
    mint_projection_capability,
)

SECRET = "s" * 48
PAYLOAD = {
    "schema_version": 1,
    "project_id": "prj_1",
    "email": "alice@example.test",
    "cloud_app_user_id": "usr_1",
    "status": "active",
    "is_owner": False,
    "project_roles": ["admin"],
    "project_permissions": ["staff"],
    "excluded_roles": [],
    "legacy_role": "admin",
    "authorization_version": 3,
}


def test_capability_is_narrow_short_lived_and_bound_to_the_project():
    token = mint_projection_capability("prj_1", SECRET, now=1_800_000_000)
    claims = jwt.decode(token, SECRET, algorithms=["HS256"], audience=CORE_AUDIENCE,
                        options={"verify_iat": False, "verify_exp": False})
    assert claims["iss"] == CORE_ISSUER
    assert claims["aud"] == CORE_AUDIENCE
    assert claims["capability"] == WRITE_CAPABILITY
    assert claims["project_id"] == "prj_1"
    assert claims["exp"] - claims["iat"] <= 120


def test_short_secret_is_refused():
    with pytest.raises(ValueError):
        mint_projection_capability("prj_1", "short")


def test_missing_base_url_is_refused():
    with pytest.raises(ValueError):
        CloudAuthProjectionWriter("", SECRET)


def _writer(handler):
    return CloudAuthProjectionWriter(
        "https://cloud.example.test", SECRET, transport=httpx.MockTransport(handler)
    )


@pytest.mark.asyncio
async def test_put_sends_the_exact_payload_to_the_projection_route_with_the_capability():
    seen = {}

    def handler(request: httpx.Request) -> httpx.Response:
        seen["url"] = str(request.url)
        seen["auth"] = request.headers["authorization"]
        seen["body"] = json.loads(request.content)
        return httpx.Response(200, json={"changed": True, "authorization_version": 3})

    result = await _writer(handler).put_projection("prj_1", "usr_1", PAYLOAD)
    assert result.ok and result.changed is True
    assert seen["url"] == (
        "https://cloud.example.test/v1/cloud/core/projects/prj_1/auth/users/usr_1/projection"
    )
    assert seen["body"] == PAYLOAD
    token = seen["auth"].removeprefix("Bearer ")
    assert jwt.get_unverified_claims(token)["capability"] == WRITE_CAPABILITY


@pytest.mark.asyncio
async def test_cloud_refusals_are_reported_as_codes_and_never_raised():
    async def run(status, body):
        writer = _writer(lambda r: httpx.Response(status, json=body))
        return await writer.put_projection("prj_1", "usr_1", PAYLOAD)

    stale = await run(409, {"error": "stale_projection"})
    assert not stale.ok and stale.code == "stale_projection" and stale.status == 409
    denied = await run(401, {"error": "unauthorized"})
    assert not denied.ok and denied.code == "unauthorized"
    other = await run(500, "boom")
    assert not other.ok and other.code == "http_500"


@pytest.mark.asyncio
async def test_transport_failure_is_a_pending_result_not_an_exception():
    def handler(request: httpx.Request) -> httpx.Response:
        raise httpx.ConnectError("down", request=request)

    result = await _writer(handler).put_projection("prj_1", "usr_1", PAYLOAD)
    assert not result.ok and result.code == "transport_error"


@pytest.mark.asyncio
async def test_malformed_success_body_is_not_treated_as_synced():
    result = await _writer(lambda r: httpx.Response(200, content=b"not json")).put_projection(
        "prj_1", "usr_1", PAYLOAD
    )
    assert not result.ok and result.code == "invalid_response"


def _settings(enabled, base="https://cloud.example.test", secret=SECRET):
    from types import SimpleNamespace

    return SimpleNamespace(
        project_membership_projection_enabled=enabled,
        cloud_control_api_url=base,
        cloud_auth_projection_secret=secret,
    )


def test_no_writer_exists_while_projection_is_disabled_or_unconfigured():
    from src.domain.project_auth.membership_projection_sync import build_projection_writer

    assert build_projection_writer(_settings(False)) is None
    assert build_projection_writer(_settings(True, base="")) is None
    assert build_projection_writer(_settings(True, secret="short")) is None
    assert isinstance(build_projection_writer(_settings(True)), CloudAuthProjectionWriter)
