import base64
import uuid

import pytest
from app.computer_capabilities import issue_computer_capability
from app.config import get_settings
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from hayva_computer.capabilities import verify_computer_capability
from hayva_computer.config import Settings as ComputerSettings
from hayva_computer.errors import ComputerRuntimeError


def encoded(value: bytes) -> str:
    return base64.urlsafe_b64encode(value).decode("ascii").rstrip("=")


@pytest.fixture
def capability_settings():
    private = Ed25519PrivateKey.generate()
    private_raw = private.private_bytes(
        serialization.Encoding.Raw,
        serialization.PrivateFormat.Raw,
        serialization.NoEncryption(),
    )
    public_raw = private.public_key().public_bytes(
        serialization.Encoding.Raw, serialization.PublicFormat.Raw
    )
    core_settings = get_settings()
    old_key = core_settings.computer_capability_private_key
    old_file = core_settings.computer_capability_private_key_file
    core_settings.computer_capability_private_key = encoded(private_raw)
    core_settings.computer_capability_private_key_file = None
    worker_settings = ComputerSettings(
        computer_service_token="worker-service-token-that-is-longer-than-32-characters",
        computer_capability_public_key=encoded(public_raw),
    )
    try:
        yield worker_settings
    finally:
        core_settings.computer_capability_private_key = old_key
        core_settings.computer_capability_private_key_file = old_file


def test_capability_is_signed_short_lived_and_bound_to_operation(capability_settings):
    session_id, workspace_id = uuid.uuid4(), uuid.uuid4()
    token = issue_computer_capability(
        scope="browser:navigate", request_id="request-1234",
        session_id=session_id, workspace_id=workspace_id, fencing_token=7,
    )
    claims = verify_computer_capability(
        token, settings=capability_settings, expected_scope="browser:navigate",
        request_id="request-1234", session_id=session_id,
        workspace_id=workspace_id, fencing_token=7,
    )
    assert claims.session_id == session_id
    assert claims.workspace_id == workspace_id
    assert claims.fencing_token == 7
    assert claims.expires_at > 0

    for changed in (
        {"expected_scope": "browser:click"},
        {"request_id": "request-5678"},
        {"session_id": uuid.uuid4()},
        {"workspace_id": uuid.uuid4()},
        {"fencing_token": 8},
    ):
        kwargs = {
            "expected_scope": "browser:navigate", "request_id": "request-1234",
            "session_id": session_id, "workspace_id": workspace_id, "fencing_token": 7,
            **changed,
        }
        with pytest.raises(ComputerRuntimeError):
            verify_computer_capability(token, settings=capability_settings, **kwargs)


def test_capability_rejects_tampering_expiry_and_unknown_key(capability_settings):
    token = issue_computer_capability(
        scope="session:stop", request_id="request-1234",
        session_id=uuid.uuid4(), fencing_token=2,
    )
    parts = token.split(".")
    parts[2] = ("A" if parts[2][0] != "A" else "B") + parts[2][1:]
    tampered = ".".join(parts)
    with pytest.raises(ComputerRuntimeError) as invalid:
        verify_computer_capability(
            tampered, settings=capability_settings, expected_scope="session:stop",
            request_id="request-1234",
        )
    assert invalid.value.code == "COMPUTER_CAPABILITY_INVALID"

    expired = issue_computer_capability(
        scope="session:stop", request_id="request-1234", lifetime_seconds=-1,
    )
    with pytest.raises(ComputerRuntimeError) as expired_error:
        verify_computer_capability(
            expired, settings=capability_settings, expected_scope="session:stop",
            request_id="request-1234",
        )
    assert expired_error.value.code == "COMPUTER_CAPABILITY_EXPIRED"

    other_private = Ed25519PrivateKey.generate()
    other_public = other_private.public_key().public_bytes(
        serialization.Encoding.Raw, serialization.PublicFormat.Raw
    )
    wrong_settings = ComputerSettings(
        computer_service_token="worker-service-token-that-is-longer-than-32-characters",
        computer_capability_public_key=encoded(other_public),
    )
    with pytest.raises(ComputerRuntimeError):
        verify_computer_capability(
            token, settings=wrong_settings, expected_scope="session:stop",
            request_id="request-1234",
        )
