from __future__ import annotations

import json

import pytest
import respx
import httpx

from surfinguard import Guard, BatchResult, CheckResult, SessionInfo, Policy, RiskLevel
from surfinguard.models import ChainDetection

from conftest import SAFE_RESPONSE


BATCH_RESPONSE = {
    "results": [
        {**SAFE_RESPONSE},
        {
            "allow": True,
            "score": 4,
            "level": "CAUTION",
            "primitive": "EXFILTRATION",
            "primitiveScores": [
                {"primitive": "EXFILTRATION", "score": 4, "reasons": ["Sensitive file"]}
            ],
            "reasons": ["Sensitive file access"],
            "alternatives": None,
            "latencyMs": 1.8,
            "contextBoost": 2,
            "chainDetections": None,
            "riskTrend": None,
        },
    ],
    "sessionId": "sess_abc123",
    "chainDetections": [
        {
            "chainId": "CH01",
            "name": "Reconnaissance → Exploitation",
            "matchedSteps": ["file_read", "command"],
            "scoreBoost": 3,
        }
    ],
    "overallLevel": "CAUTION",
    "overallScore": 4,
}

SESSION_RESPONSE = {
    "sessionId": "sess_abc123",
    "agentId": "agent-1",
    "startedAt": 1700000000,
    "lastActivityAt": 1700001000,
    "actionCount": 5,
    "peakScore": 7,
    "riskTrend": "rising",
    "activeChains": ["CH01"],
}

POLICY_RESPONSE = {
    "id": "pol_123",
    "name": "My Policy",
    "level": "balanced",
    "rules": [],
    "allowlist": [],
    "blocklist": [],
    "environments": [],
    "isActive": False,
}


class TestCheckBatch:
    def test_basic_batch(self, mock_api, guard):
        mock_api.post("/v2/check/batch").mock(
            return_value=httpx.Response(200, json=BATCH_RESPONSE)
        )
        result = guard.check_batch([
            {"type": "url", "value": "https://google.com"},
            {"type": "file_read", "value": "/etc/passwd"},
        ])
        assert isinstance(result, BatchResult)
        assert len(result.results) == 2
        assert result.session_id == "sess_abc123"
        assert result.overall_level == RiskLevel.CAUTION
        assert result.overall_score == 4

    def test_batch_chain_detections(self, mock_api, guard):
        mock_api.post("/v2/check/batch").mock(
            return_value=httpx.Response(200, json=BATCH_RESPONSE)
        )
        result = guard.check_batch([{"type": "url", "value": "https://google.com"}])
        assert len(result.chain_detections) == 1
        assert result.chain_detections[0].chain_id == "CH01"
        assert result.chain_detections[0].score_boost == 3

    def test_batch_sends_correct_payload(self, mock_api, guard):
        route = mock_api.post("/v2/check/batch").mock(
            return_value=httpx.Response(200, json=BATCH_RESPONSE)
        )
        actions = [{"type": "command", "value": "ls"}]
        guard.check_batch(actions, sequential=False, enhance=True)
        body = json.loads(route.calls[0].request.content)
        assert body["actions"] == actions
        assert body["sequential"] is False
        assert body["enhance"] is True

    def test_batch_with_session_id(self, mock_api):
        mock_api.post("/v2/check/batch").mock(
            return_value=httpx.Response(200, json=BATCH_RESPONSE)
        )
        g = Guard(
            api_key="sg_test_0123456789abcdef0123456789abcdef",
            base_url="https://test.surfinguard.com",
            session_id="sess_xyz",
            agent_id="agent-42",
        )
        route = mock_api.post("/v2/check/batch").mock(
            return_value=httpx.Response(200, json=BATCH_RESPONSE)
        )
        g.check_batch([{"type": "url", "value": "https://example.com"}])
        body = json.loads(route.calls[0].request.content)
        assert body["sessionId"] == "sess_xyz"
        assert body["agentId"] == "agent-42"

    def test_batch_override_session_id(self, mock_api):
        mock_api.post("/v2/check/batch").mock(
            return_value=httpx.Response(200, json=BATCH_RESPONSE)
        )
        g = Guard(
            api_key="sg_test_0123456789abcdef0123456789abcdef",
            base_url="https://test.surfinguard.com",
            session_id="sess_default",
        )
        route = mock_api.post("/v2/check/batch").mock(
            return_value=httpx.Response(200, json=BATCH_RESPONSE)
        )
        g.check_batch(
            [{"type": "url", "value": "https://example.com"}],
            session_id="sess_override",
        )
        body = json.loads(route.calls[0].request.content)
        assert body["sessionId"] == "sess_override"


class TestGetSession:
    def test_get_session(self, mock_api):
        g = Guard(
            api_key="sg_test_0123456789abcdef0123456789abcdef",
            base_url="https://test.surfinguard.com",
            session_id="sess_abc123",
        )
        mock_api.get("/v2/sessions/sess_abc123").mock(
            return_value=httpx.Response(200, json=SESSION_RESPONSE)
        )
        result = g.get_session()
        assert isinstance(result, SessionInfo)
        assert result.session_id == "sess_abc123"
        assert result.agent_id == "agent-1"
        assert result.action_count == 5
        assert result.peak_score == 7
        assert result.risk_trend == "rising"
        assert result.active_chains == ["CH01"]

    def test_get_session_explicit_id(self, mock_api, guard):
        mock_api.get("/v2/sessions/sess_other").mock(
            return_value=httpx.Response(200, json=SESSION_RESPONSE)
        )
        result = guard.get_session("sess_other")
        assert isinstance(result, SessionInfo)

    def test_get_session_no_id_raises(self, guard):
        with pytest.raises(ValueError, match="No session_id"):
            guard.get_session()


class TestResetSession:
    def test_reset_session(self, mock_api):
        g = Guard(
            api_key="sg_test_0123456789abcdef0123456789abcdef",
            base_url="https://test.surfinguard.com",
            session_id="sess_abc123",
        )
        mock_api.delete("/v2/sessions/sess_abc123").mock(
            return_value=httpx.Response(200, json={"ok": True})
        )
        g.reset_session()

    def test_reset_session_explicit_id(self, mock_api, guard):
        mock_api.delete("/v2/sessions/sess_other").mock(
            return_value=httpx.Response(200, json={"ok": True})
        )
        guard.reset_session("sess_other")

    def test_reset_session_no_id_raises(self, guard):
        with pytest.raises(ValueError, match="No session_id"):
            guard.reset_session()


class TestPolicies:
    def test_create_policy(self, mock_api, guard):
        route = mock_api.post("/v2/policies").mock(
            return_value=httpx.Response(200, json=POLICY_RESPONSE)
        )
        result = guard.create_policy("My Policy", level="strict")
        body = json.loads(route.calls[0].request.content)
        assert body["name"] == "My Policy"
        assert body["level"] == "strict"
        assert result["id"] == "pol_123"

    def test_list_policies(self, mock_api, guard):
        mock_api.get("/v2/policies").mock(
            return_value=httpx.Response(200, json={"policies": [POLICY_RESPONSE]})
        )
        result = guard.list_policies()
        assert len(result["policies"]) == 1

    def test_activate_policy(self, mock_api, guard):
        mock_api.post("/v2/policies/pol_123/activate").mock(
            return_value=httpx.Response(200, json={"ok": True})
        )
        result = guard.activate_policy("pol_123")
        assert result["ok"] is True


class TestContextFields:
    def test_check_result_with_context_boost(self, mock_api, guard):
        response = {
            **SAFE_RESPONSE,
            "contextBoost": 3,
            "chainDetections": [
                {
                    "chainId": "CH02",
                    "name": "Credential Harvesting",
                    "matchedSteps": ["file_read", "api_call"],
                    "scoreBoost": 4,
                }
            ],
            "riskTrend": "rising",
        }
        mock_api.post("/v2/check/url").mock(
            return_value=httpx.Response(200, json=response)
        )
        result = guard.check_url("https://example.com")
        assert result.context_boost == 3
        assert result.risk_trend == "rising"
        assert len(result.chain_detections) == 1
        assert result.chain_detections[0].chain_id == "CH02"

    def test_check_result_without_context_fields(self, mock_api, guard):
        mock_api.post("/v2/check/url").mock(
            return_value=httpx.Response(200, json=SAFE_RESPONSE)
        )
        result = guard.check_url("https://example.com")
        assert result.context_boost is None
        assert result.chain_detections is None
        assert result.risk_trend is None

    def test_health_includes_session_policy_flags(self, mock_api, guard):
        health = {
            "ok": True,
            "version": "4.0.0",
            "analyzers": ["url", "command", "text", "file_read", "file_write",
                          "api_call", "query", "code"],
            "auth": True,
            "llm": True,
            "sessions": True,
            "policies": True,
            "uptime": 7200,
        }
        mock_api.get("/v2/health").mock(
            return_value=httpx.Response(200, json=health)
        )
        result = guard.health()
        assert result.sessions is True
        assert result.policies is True
        assert result.version == "4.0.0"
