"""
Tests for VantageClient using respx to mock httpx.
"""
import pytest
import respx
import httpx

from vantageai import VantageClient
from vantageai.types import (
    ExecutionTrace,
    IntentTrace,
    IntentShift,
    IntentCloseResult,
    EvaluationResult,
    ApprovalRequest,
)

BASE_URL = "https://agentosapi-production.up.railway.app"
TEST_API_KEY = "test-key-123"
TEST_AGENT_ID = "agent-abc"


def make_client() -> VantageClient:
    return VantageClient(api_key=TEST_API_KEY, agent_id=TEST_AGENT_ID, base_url=BASE_URL)


# ── log_event ─────────────────────────────────────────────────

@respx.mock
@pytest.mark.asyncio
async def test_log_event_post_correct_body():
    route = respx.post(f"{BASE_URL}/v1/events").mock(
        return_value=httpx.Response(200, json={"id": "evt-1", "timestamp": "2026-01-01T00:00:00Z"})
    )
    client = make_client()
    result = await client.log_event(type="llm_call", input={"model": "gpt-4o"})

    assert route.called
    body = route.calls[0].request.content
    import json
    parsed = json.loads(body)
    assert parsed["type"] == "llm_call"
    assert parsed["agentId"] == TEST_AGENT_ID
    assert parsed["input"] == {"model": "gpt-4o"}
    assert result["id"] == "evt-1"


@respx.mock
@pytest.mark.asyncio
async def test_log_event_auth_header():
    route = respx.post(f"{BASE_URL}/v1/events").mock(
        return_value=httpx.Response(200, json={"id": "evt-1", "timestamp": "2026-01-01T00:00:00Z"})
    )
    client = make_client()
    await client.log_event(type="llm_call")

    assert route.calls[0].request.headers["Authorization"] == f"Bearer {TEST_API_KEY}"


@respx.mock
@pytest.mark.asyncio
async def test_log_event_never_throws_on_network_error():
    respx.post(f"{BASE_URL}/v1/events").mock(side_effect=httpx.ConnectError("Network failure"))
    client = make_client()
    # Must not raise — returns fallback
    result = await client.log_event(type="llm_call")
    assert result["id"] == ""


# ── Execution Trace ────────────────────────────────────────────

MOCK_TRACE = {
    "id": "trace-1",
    "orgId": "org-1",
    "agentId": TEST_AGENT_ID,
    "sessionId": "session-abc",
    "status": "open",
    "stepCount": 0,
    "toolCalls": [],
    "startedAt": "2026-01-01T00:00:00Z",
}


@respx.mock
@pytest.mark.asyncio
async def test_start_execution():
    route = respx.post(f"{BASE_URL}/v1/execution-traces").mock(
        return_value=httpx.Response(200, json=MOCK_TRACE)
    )
    client = make_client()
    trace = await client.start_execution(session_id="session-abc")

    assert route.called
    import json
    body = json.loads(route.calls[0].request.content)
    assert body["agentId"] == TEST_AGENT_ID
    assert body["sessionId"] == "session-abc"
    assert isinstance(trace, ExecutionTrace)
    assert trace.id == "trace-1"


@respx.mock
@pytest.mark.asyncio
async def test_log_step():
    updated = {**MOCK_TRACE, "stepCount": 1}
    route = respx.patch(f"{BASE_URL}/v1/execution-traces/trace-1").mock(
        return_value=httpx.Response(200, json=updated)
    )
    client = make_client()
    step = {
        "stepNumber": 1,
        "type": "tool_call",
        "toolName": "send_email",
        "toolArgs": {"to": "customer@acme.com"},
        "toolResult": {"sent": True},
        "latencyMs": 340,
    }
    result = await client.log_step("trace-1", step)

    assert route.called
    import json
    body = json.loads(route.calls[0].request.content)
    assert body["step"]["stepNumber"] == 1
    assert body["step"]["toolName"] == "send_email"
    assert isinstance(result, ExecutionTrace)
    assert result.stepCount == 1


@respx.mock
@pytest.mark.asyncio
async def test_complete_execution():
    completed = {**MOCK_TRACE, "status": "complete", "durationMs": 1200}
    route = respx.post(f"{BASE_URL}/v1/execution-traces/trace-1/complete").mock(
        return_value=httpx.Response(200, json=completed)
    )
    client = make_client()
    result = await client.complete_execution("trace-1", status="complete")

    assert route.called
    import json
    body = json.loads(route.calls[0].request.content)
    assert body["status"] == "complete"
    assert isinstance(result, ExecutionTrace)
    assert result.status == "complete"
    assert result.durationMs == 1200


@respx.mock
@pytest.mark.asyncio
async def test_full_execution_flow():
    respx.post(f"{BASE_URL}/v1/execution-traces").mock(
        return_value=httpx.Response(200, json={**MOCK_TRACE, "id": "trace-flow"})
    )
    respx.patch(f"{BASE_URL}/v1/execution-traces/trace-flow").mock(
        return_value=httpx.Response(200, json={**MOCK_TRACE, "id": "trace-flow", "stepCount": 1})
    )
    respx.post(f"{BASE_URL}/v1/execution-traces/trace-flow/complete").mock(
        return_value=httpx.Response(200, json={**MOCK_TRACE, "id": "trace-flow", "status": "complete", "durationMs": 800})
    )

    client = make_client()
    trace = await client.start_execution(session_id="flow-session")
    assert trace.id == "trace-flow"

    updated = await client.log_step(trace.id, {"stepNumber": 1, "type": "tool_call", "toolName": "search", "latencyMs": 200})
    assert updated.stepCount == 1

    done = await client.complete_execution(trace.id, status="complete")
    assert done.status == "complete"
    assert done.durationMs == 800


# ── Intent Trace ───────────────────────────────────────────────

MOCK_INTENT = {
    "id": "intent-1",
    "orgId": "org-1",
    "agentId": TEST_AGENT_ID,
    "declaredIntent": "Draft a reply to the customer complaint email",
    "status": "open",
    "createdAt": "2026-01-01T00:00:00Z",
}


@respx.mock
@pytest.mark.asyncio
async def test_start_intent():
    route = respx.post(f"{BASE_URL}/v1/intent-traces").mock(
        return_value=httpx.Response(200, json=MOCK_INTENT)
    )
    client = make_client()
    intent = await client.start_intent(
        declared_intent="Draft a reply to the customer complaint email",
        execution_trace_id="trace-1",
    )

    assert route.called
    import json
    body = json.loads(route.calls[0].request.content)
    assert body["agentId"] == TEST_AGENT_ID
    assert body["declaredIntent"] == "Draft a reply to the customer complaint email"
    assert body["executionTraceId"] == "trace-1"
    assert isinstance(intent, IntentTrace)
    assert intent.id == "intent-1"


@respx.mock
@pytest.mark.asyncio
async def test_log_shift_returns_decision():
    mock_shift = {
        "id": "shift-blocked",
        "intentTraceId": "intent-1",
        "observedIntent": "Approve duplicate payment of $28,000",
        "severity": "critical",
        "timestamp": "2026-01-01T00:01:00Z",
        "decision": "block",
        "evaluationId": "eval-abc",
    }
    route = respx.post(f"{BASE_URL}/v1/intent-traces/intent-1/shifts").mock(
        return_value=httpx.Response(200, json=mock_shift)
    )
    client = make_client()
    shift = await client.log_shift(
        "intent-1",
        observed_intent="Approve duplicate payment of $28,000",
        severity="critical",
    )

    assert route.called
    assert isinstance(shift, IntentShift)
    assert shift.decision == "block"
    assert shift.evaluationId == "eval-abc"
    assert shift.approvalRequestId is None


@respx.mock
@pytest.mark.asyncio
async def test_close_intent_returns_fidelity_score():
    mock_result = {"fidelityScore": 0.3, "shifts": 1, "status": "flagged"}
    route = respx.post(f"{BASE_URL}/v1/intent-traces/intent-1/close").mock(
        return_value=httpx.Response(200, json=mock_result)
    )
    client = make_client()
    result = await client.close_intent("intent-1", resolved_intent="Sent email directly")

    assert route.called
    assert isinstance(result, IntentCloseResult)
    assert result.fidelityScore == 0.3
    assert result.shifts == 1
    assert result.status == "flagged"


@respx.mock
@pytest.mark.asyncio
async def test_full_intent_flow_critical_blocked():
    respx.post(f"{BASE_URL}/v1/intent-traces").mock(
        return_value=httpx.Response(200, json={**MOCK_INTENT, "id": "intent-flow"})
    )
    respx.post(f"{BASE_URL}/v1/intent-traces/intent-flow/shifts").mock(
        return_value=httpx.Response(200, json={
            "id": "shift-flow",
            "intentTraceId": "intent-flow",
            "observedIntent": "Send email directly without drafting first",
            "severity": "critical",
            "timestamp": "2026-01-01T00:01:00Z",
            "decision": "block",
            "evaluationId": "eval-flow",
        })
    )
    respx.post(f"{BASE_URL}/v1/intent-traces/intent-flow/close").mock(
        return_value=httpx.Response(200, json={"fidelityScore": 0.3, "shifts": 1, "status": "flagged"})
    )

    client = make_client()
    intent = await client.start_intent(
        declared_intent="Draft a reply to the customer complaint email",
        execution_trace_id="trace-1",
    )
    assert intent.id == "intent-flow"

    shift = await client.log_shift(
        intent.id,
        observed_intent="Send email directly without drafting first",
        severity="critical",
        shift_reason="draft_tool unavailable",
    )
    assert shift.decision == "block"
    assert shift.severity == "critical"

    close_result = await client.close_intent(intent.id, resolved_intent="Sent email directly without user review")
    assert close_result.fidelityScore == 0.3
    assert close_result.status == "flagged"


# ── Policy Engine ──────────────────────────────────────────────

@respx.mock
@pytest.mark.asyncio
async def test_evaluate_allow():
    route = respx.post(f"{BASE_URL}/v1/evaluate").mock(
        return_value=httpx.Response(200, json={"decision": "allow", "evaluationId": "eval-1"})
    )
    client = make_client()
    result = await client.evaluate(
        action_type="read_document",
        action_payload={"docId": "doc-123"},
    )

    assert route.called
    import json
    body = json.loads(route.calls[0].request.content)
    assert body["agentId"] == TEST_AGENT_ID
    assert body["actionType"] == "read_document"
    assert isinstance(result, EvaluationResult)
    assert result.decision == "allow"


@respx.mock
@pytest.mark.asyncio
async def test_evaluate_block():
    respx.post(f"{BASE_URL}/v1/evaluate").mock(
        return_value=httpx.Response(200, json={
            "decision": "block",
            "reason": "Action blocked by policy: no external emails",
            "evaluationId": "eval-2",
        })
    )
    client = make_client()
    result = await client.evaluate(
        action_type="send_email",
        action_payload={"to": "external@company.com"},
    )

    assert result.decision == "block"
    assert result.reason == "Action blocked by policy: no external emails"


@respx.mock
@pytest.mark.asyncio
async def test_evaluate_pending_approval():
    respx.post(f"{BASE_URL}/v1/evaluate").mock(
        return_value=httpx.Response(200, json={
            "decision": "pending_approval",
            "evaluationId": "eval-3",
            "approvalRequestId": "approval-99",
        })
    )
    client = make_client()
    result = await client.evaluate(
        action_type="approve_payment",
        action_payload={"amount": 50000},
    )

    assert result.decision == "pending_approval"
    assert result.approvalRequestId == "approval-99"


@respx.mock
@pytest.mark.asyncio
async def test_wait_for_approval_resolves():
    approved = {
        "id": "approval-99",
        "orgId": "org-1",
        "policyEvaluationId": "eval-3",
        "agentId": TEST_AGENT_ID,
        "actionType": "approve_payment",
        "actionPayload": {"amount": 50000},
        "status": "approved",
        "requestedAt": "2026-01-01T00:00:00Z",
        "resolvedAt": "2026-01-01T00:01:00Z",
        "resolvedBy": "human",
    }
    respx.get(f"{BASE_URL}/v1/approvals/approval-99").mock(
        return_value=httpx.Response(200, json=approved)
    )
    client = make_client()
    approval = await client.wait_for_approval("approval-99", timeout_ms=5000)

    assert isinstance(approval, ApprovalRequest)
    assert approval.status == "approved"
    assert approval.id == "approval-99"
    assert approval.resolvedBy == "human"


@respx.mock
@pytest.mark.asyncio
async def test_wait_for_approval_polls_until_resolved():
    pending = {"id": "approval-88", "orgId": "org-1", "policyEvaluationId": "e", "agentId": TEST_AGENT_ID, "actionType": "a", "actionPayload": {}, "status": "pending", "requestedAt": "2026-01-01T00:00:00Z"}
    approved = {**pending, "status": "approved", "resolvedAt": "2026-01-01T00:00:02Z"}

    respx.get(f"{BASE_URL}/v1/approvals/approval-88").mock(
        side_effect=[
            httpx.Response(200, json=pending),
            httpx.Response(200, json=approved),
        ]
    )
    client = make_client()
    result = await client.wait_for_approval("approval-88", timeout_ms=10_000, poll_interval_ms=10)

    assert result.status == "approved"


@respx.mock
@pytest.mark.asyncio
async def test_wait_for_approval_timeout():
    pending = {"id": "approval-77", "orgId": "org-1", "policyEvaluationId": "e", "agentId": TEST_AGENT_ID, "actionType": "a", "actionPayload": {}, "status": "pending", "requestedAt": "2026-01-01T00:00:00Z"}
    respx.get(f"{BASE_URL}/v1/approvals/approval-77").mock(
        return_value=httpx.Response(200, json=pending)
    )
    client = make_client()
    with pytest.raises(TimeoutError, match="timed out"):
        await client.wait_for_approval("approval-77", timeout_ms=50, poll_interval_ms=10)
