"""Tests for ApprovalAdmin (via Primitif)."""

import pytest
import respx
import httpx

from primitif import Primitif, AuthError
from primitif.approval import (
    ApprovalError,
    ApprovalAuthError,
    ApprovalRequest,
)


APPROVAL = "https://api.approval.primitif.ai"


@respx.mock
def test_create_request():
    respx.post(f"{APPROVAL}/v1/requests").mock(
        return_value=httpx.Response(200, json={
            "id": "ar_abc", "action": "Send contract",
            "context": {"amount": 45000}, "status": "pending",
            "approval_url": "https://dash.primitif.ai/approve/ar_abc",
            "created_at": "2025-01-01T00:00:00Z",
        })
    )
    p = Primitif(api_key="pk_live_test12345678901234567890123456")
    req = p.approval.create_request(
        action="Send contract",
        context={"amount": 45000},
    )
    assert isinstance(req, ApprovalRequest)
    assert req.status == "pending"
    assert req.id == "ar_abc"
    p.close()


@respx.mock
def test_create_request_with_callback():
    respx.post(f"{APPROVAL}/v1/requests").mock(
        return_value=httpx.Response(200, json={
            "id": "ar_def", "action": "Delete account",
            "status": "pending",
            "approval_url": "https://dash.primitif.ai/approve/ar_def",
            "created_at": "2025-01-01T00:00:00Z",
        })
    )
    p = Primitif(api_key="pk_live_test12345678901234567890123456")
    req = p.approval.create_request(
        action="Delete account",
        callback_url="https://myapp.com/webhook",
        requested_by="agent-007",
    )
    assert req.id == "ar_def"
    p.close()


@respx.mock
def test_get_request():
    respx.get(f"{APPROVAL}/v1/requests/ar_abc").mock(
        return_value=httpx.Response(200, json={
            "id": "ar_abc", "action": "Send contract",
            "status": "approved",
            "approval_url": "https://dash.primitif.ai/approve/ar_abc",
            "created_at": "2025-01-01T00:00:00Z",
        })
    )
    p = Primitif(api_key="pk_live_test12345678901234567890123456")
    req = p.approval.get_request("ar_abc")
    assert req.status == "approved"
    p.close()


@respx.mock
def test_list_requests():
    respx.get(f"{APPROVAL}/v1/requests").mock(
        return_value=httpx.Response(200, json={
            "data": [
                {"id": "ar_1", "action": "A", "status": "pending",
                 "approval_url": "https://x", "created_at": "2025-01-01T00:00:00Z"},
                {"id": "ar_2", "action": "B", "status": "approved",
                 "approval_url": "https://x", "created_at": "2025-01-01T00:00:00Z"},
            ]
        })
    )
    p = Primitif(api_key="pk_live_test12345678901234567890123456")
    requests = p.approval.list_requests()
    assert len(requests) == 2
    assert requests[0].id == "ar_1"
    p.close()


@respx.mock
def test_list_requests_items_key():
    """list_requests handles both 'items' and 'data' keys."""
    respx.get(f"{APPROVAL}/v1/requests").mock(
        return_value=httpx.Response(200, json={
            "items": [
                {"id": "ar_1", "action": "A", "status": "pending",
                 "approval_url": "https://x", "created_at": "2025-01-01T00:00:00Z"},
            ]
        })
    )
    p = Primitif(api_key="pk_live_test12345678901234567890123456")
    requests = p.approval.list_requests()
    assert len(requests) == 1
    p.close()


@respx.mock
def test_401_raises_approval_auth_error():
    respx.post(f"{APPROVAL}/v1/requests").mock(
        return_value=httpx.Response(401, json={
            "detail": {"error": {"code": "unauthorized", "message": "Bad token"}}
        })
    )
    p = Primitif(api_key="pk_live_test12345678901234567890123456")
    with pytest.raises(ApprovalAuthError):
        p.approval.create_request(action="test")
    p.close()


def test_diamond_inheritance():
    """ApprovalAuthError is catchable as both ApprovalError and AuthError."""
    err = ApprovalAuthError(message="test", status_code=401)
    assert isinstance(err, ApprovalError)
    assert isinstance(err, AuthError)


@respx.mock
def test_primitif_context_manager():
    respx.post(f"{APPROVAL}/v1/requests").mock(
        return_value=httpx.Response(200, json={
            "id": "ar_abc", "action": "test", "status": "pending",
            "approval_url": "https://x", "created_at": "2025-01-01T00:00:00Z",
        })
    )
    with Primitif(api_key="pk_live_test12345678901234567890123456") as p:
        req = p.approval.create_request(action="test")
        assert req.id == "ar_abc"


def test_empty_api_key_raises():
    from primitif._exceptions import PrimitifError
    with pytest.raises(PrimitifError, match="No API key"):
        Primitif(api_key="")


def test_whitespace_api_key_raises():
    from primitif._exceptions import PrimitifError
    with pytest.raises(PrimitifError, match="No API key"):
        Primitif(api_key="   ")


@respx.mock
def test_require_approval_decorator():
    """@require_approval creates a pending request and exposes .original."""
    route = respx.post(f"{APPROVAL}/v1/requests").mock(
        return_value=httpx.Response(200, json={
            "id": "ar_dec", "action": "Send invoice",
            "context": {"client": "Acme", "amount": 45000},
            "status": "pending",
            "approval_url": "https://dash.primitif.ai/approve/ar_dec",
            "created_at": "2025-01-01T00:00:00Z",
        })
    )
    p = Primitif(api_key="pk_live_test12345678901234567890123456")

    @p.approval.require_approval("Send invoice")
    def send_invoice(client, amount):
        return f"sent {amount} to {client}"

    result = send_invoice("Acme", 45000)
    assert isinstance(result, ApprovalRequest)
    assert result.status == "pending"
    assert result.id == "ar_dec"

    # .original holds the real function
    assert send_invoice.original("Acme", 45000) == "sent 45000 to Acme"

    # verify the POST payload
    req = route.calls[0].request
    import json
    body = json.loads(req.content)
    assert body["action"] == "Send invoice"
    assert body["context"] == {"client": "Acme", "amount": 45000}
    p.close()


@respx.mock
def test_require_approval_auto_action():
    """Without explicit action, the decorator derives it from the function name."""
    respx.post(f"{APPROVAL}/v1/requests").mock(
        return_value=httpx.Response(200, json={
            "id": "ar_auto", "action": "Delete account",
            "status": "pending",
            "approval_url": "https://dash.primitif.ai/approve/ar_auto",
            "created_at": "2025-01-01T00:00:00Z",
        })
    )
    p = Primitif(api_key="pk_live_test12345678901234567890123456")

    @p.approval.require_approval()
    def delete_account(user_id):
        pass

    result = delete_account(42)
    assert isinstance(result, ApprovalRequest)
    assert result.id == "ar_auto"

    # Check the auto-derived action name
    import json
    body = json.loads(respx.calls[0].request.content)
    assert body["action"] == "Delete account"
    assert body["context"] == {"user_id": 42}

    # functools.wraps preserves the name
    assert delete_account.__name__ == "delete_account"
    p.close()


def test_dispatch_approved_event():
    """dispatch() calls the registered tool with context from the webhook event."""
    p = Primitif(api_key="pk_live_test12345678901234567890123456")

    @p.approval.require_approval("Send invoice")
    def send_invoice(client, amount):
        return f"sent {amount} to {client}"

    event = {
        "action": "Send invoice",
        "status": "approved",
        "context": {"client": "Acme", "amount": 45000},
    }
    assert p.approval.dispatch(event) == "sent 45000 to Acme"
    p.close()


def test_dispatch_ignores_non_approved():
    """dispatch() returns None for rejected/pending events."""
    p = Primitif(api_key="pk_live_test12345678901234567890123456")

    @p.approval.require_approval("Send invoice")
    def send_invoice(client, amount):
        return f"sent {amount} to {client}"

    event = {
        "action": "Send invoice",
        "status": "rejected",
        "context": {"client": "Acme", "amount": 45000},
    }
    assert p.approval.dispatch(event) is None
    p.close()


def test_dispatch_unknown_action_raises():
    """dispatch() raises ApprovalError for unregistered actions."""
    p = Primitif(api_key="pk_live_test12345678901234567890123456")

    event = {
        "action": "Unknown action",
        "status": "approved",
        "context": {},
    }
    with pytest.raises(ApprovalError, match="No handler registered"):
        p.approval.dispatch(event)
    p.close()


def test_duplicate_action_registration_raises():
    """Registering two functions with the same action name raises ValueError."""
    p = Primitif(api_key="pk_live_test12345678901234567890123456")

    @p.approval.require_approval("Send invoice")
    def send_invoice_v1(client, amount):
        pass

    with pytest.raises(ValueError, match="already registered"):
        @p.approval.require_approval("Send invoice")
        def send_invoice_v2(client, amount):
            pass

    p.close()


@respx.mock
def test_dispatch_minimal_payload_fetches_request():
    """dispatch() fetches the full record when only request_id + status are present."""
    respx.get(f"{APPROVAL}/v1/requests/ar_min").mock(
        return_value=httpx.Response(200, json={
            "id": "ar_min",
            "action": "Send invoice",
            "context": {"client": "Acme", "amount": 45000},
            "status": "approved",
            "approval_url": "https://dash.primitif.ai/approve/ar_min",
            "created_at": "2025-01-01T00:00:00Z",
        })
    )
    p = Primitif(api_key="pk_live_test12345678901234567890123456")

    @p.approval.require_approval("Send invoice")
    def send_invoice(client, amount):
        return f"sent {amount} to {client}"

    # Minimal webhook payload — just request_id + status
    event = {"request_id": "ar_min", "status": "approved"}
    assert p.approval.dispatch(event) == "sent 45000 to Acme"

    # Verify it called GET /v1/requests/ar_min
    assert respx.calls[0].request.url.path == "/v1/requests/ar_min"
    p.close()
