from __future__ import annotations

"""Tests for the AsyncNexure SDK client — mocked HTTP."""

import os
import sys

import pytest

sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

import httpx
from nexure import AsyncNexure, NexureError
from nexure.models import (
    AnalyticsComparisons,
    AnalyticsDrift,
    AnalyticsOverrides,
    AnalyticsOverview,
    DecisionBrief,
    DecisionList,
    DecisionPackage,
    DecisionRecord,
    ForensicReport,
    LedgerVerification,
    TemplateDetail,
    TemplateInfo,
    TemplateList,
    VisibilityScore,
)
from pytest_httpx import HTTPXMock

MOCK_DECISION = {
    "id": "dec_abc123def456",
    "decision_type": "credit_underwriting",
    "sequence_number": 1,
    "hash": "a" * 64,
    "previous_hash": "0" * 64,
    "package_id": "pkg_abc123def456",
    "brief_id": None,
    "action": "approve",
    "conditions": None,
    "reasoning": None,
    "decided_by": "reviewer_schen",
    "decided_at": "2025-01-15T10:30:00Z",
    "overrode_model": False,
    "overrode_brief": False,
    "override_reason": None,
    "evidence_snapshot": {"fico_score": 711},
    "trust_score": None,
    "classification": None,
    "tenant_id": "tenant_dev",
    "created_at": "2025-01-15T10:30:00Z",
}

MOCK_PACKAGE = {
    "id": "pkg_abc123def456",
    "decision_type": "credit_underwriting",
    "subject": None,
    "evidence": {"fico_score": 711},
    "policy_context": None,
    "model_output": None,
    "source_system": None,
    "source_agent": None,
    "metadata": None,
    "tenant_id": "tenant_dev",
    "created_at": "2025-01-15T10:30:00Z",
}

MOCK_BRIEF = {
    "id": "brf_abc123def456",
    "package_id": None,
    "template_id": "credit_underwriting",
    "template_version": "1.0.0",
    "synthesis": "Strong applicant with FICO 711.",
    "key_metrics_assessment": "DTI at 0.38 slightly above guideline.",
    "risk_narrative": "Moderate DTI is the primary concern.",
    "flags": [{"id": "high_dti", "name": "Elevated DTI", "severity": "medium"}],
    "comparables": None,
    "pattern_match": "approve",
    "quality_score": 72,
    "quality_level": "strong",
    "quality_components": {"evidence_completeness": 100, "comparables_depth": 0, "flag_coverage": 100, "data_recency": 0},
    "quality_summary": "Based on complete evidence, no comparable decisions yet.",
    "model_used": "claude-sonnet-4-5-20250929",
    "generation_time_ms": 3200,
    "tenant_id": "tenant_dev",
    "created_at": "2025-01-15T10:30:00Z",
}

MOCK_TEMPLATE = {
    "id": "credit_underwriting",
    "version": "1.0.0",
    "display_name": "Credit Underwriting Review",
    "description": "Decision brief for credit decisions",
    "industry": "financial_services",
    "author": "nexure",
}

MOCK_OVERVIEW = {
    "total_decisions": 4000,
    "decisions_in_period": 650,
    "override_rate": 0.18,
    "avg_trust_score": 72.5,
    "action_breakdown": [
        {"action": "approve", "count": 2800, "percentage": 70.0},
    ],
    "decisions_by_day": [{"date": "2025-01-15", "count": 34}],
    "tenant_id": "tenant_dev",
    "period_start": "2025-01-01T00:00:00Z",
    "period_end": "2025-01-31T00:00:00Z",
}

MOCK_OVERRIDES = {
    "team_override_rate": 0.18,
    "reviewers": [
        {
            "reviewer": "M. Johnson",
            "total_decisions": 1000,
            "overrides": 410,
            "override_rate": 0.41,
            "is_anomaly": True,
            "deviation_from_mean": 2.3,
            "override_direction": {"allow_to_block": 320, "block_to_allow": 50, "other": 40},
        },
    ],
    "anomaly_threshold": 0.36,
    "by_decision_type": {"credit_underwriting": 0.18},
    "trend": [{"week": "2025-W03", "rate": 0.17}],
    "tenant_id": "tenant_dev",
}

MOCK_DRIFT = {
    "weeks": [
        {
            "week_start": "2025-01-06T00:00:00Z",
            "approval_rate": 0.72,
            "denial_rate": 0.28,
            "override_rate": 0.18,
            "decision_count": 150,
        },
    ],
    "alerts": [
        {
            "metric": "approval_rate",
            "direction": "decrease",
            "severity": "low",
            "current_value": 0.65,
            "baseline_value": 0.72,
            "change_pct": -0.10,
            "message": "Approval Rate decreased by 10%",
        },
    ],
    "tenant_id": "tenant_dev",
}

MOCK_COMPARISONS = {
    "pairs": [
        {
            "record_a_id": "dec_aaa111222333",
            "record_b_id": "dec_bbb444555666",
            "record_a_action": "approve",
            "record_b_action": "deny",
            "record_a_decided_by": "S. Chen",
            "record_b_decided_by": "M. Johnson",
            "similarity_score": 0.85,
            "key_evidence_a": {"fico_score": 710},
            "key_evidence_b": {"fico_score": 715},
            "key_differences": [],
        }
    ],
    "total_comparisons_checked": 12450,
    "tenant_id": "tenant_dev",
}

MOCK_VISIBILITY = {
    "score": 67.3,
    "components": [
        {
            "name": "Capture Rate",
            "weight": 0.25,
            "score": 72.0,
            "raw_value": 0.72,
            "description": "2880/4000 decisions have reasoning",
        },
    ],
    "total_decisions": 4000,
    "tenant_id": "tenant_dev",
}

MOCK_LEDGER = {
    "valid": True,
    "records_checked": 500,
    "first_break_at": None,
    "error": None,
    "tenant_id": "tenant_dev",
    "verified_at": "2025-01-15T10:30:00Z",
}


# ── Init / Context Manager ──────────────────────────────────────


class TestAsyncNexureInit:
    def test_missing_api_key_raises(self):
        env_key = os.environ.pop("NEXURE_API_KEY", None)
        try:
            with pytest.raises(NexureError, match="API key is required"):
                AsyncNexure()
        finally:
            if env_key:
                os.environ["NEXURE_API_KEY"] = env_key

    def test_init_with_api_key(self):
        nx = AsyncNexure(api_key="test_key", base_url="http://test:8000/v1")
        assert nx.api_key == "test_key"
        assert nx.base_url == "http://test:8000/v1"


class TestAsyncContextManager:
    @pytest.mark.asyncio
    async def test_context_manager(self):
        async with AsyncNexure(api_key="test_key", base_url="http://test:8000/v1") as nx:
            assert nx.api_key == "test_key"


# ── Decisions ────────────────────────────────────────────────────


class TestAsyncDecisions:
    @pytest.mark.asyncio
    async def test_record(self, httpx_mock: HTTPXMock):
        httpx_mock.add_response(
            method="POST",
            url="http://test:8000/v1/decisions",
            json=MOCK_DECISION,
            status_code=201,
        )
        async with AsyncNexure(api_key="test_key", base_url="http://test:8000/v1") as nx:
            result = await nx.decisions.record(
                decision_type="credit_underwriting",
                evidence={"fico_score": 711},
                action="approve",
                decided_by="reviewer_schen",
            )
        assert isinstance(result, DecisionRecord)
        assert result.id == "dec_abc123def456"

    @pytest.mark.asyncio
    async def test_get(self, httpx_mock: HTTPXMock):
        httpx_mock.add_response(
            method="GET",
            url="http://test:8000/v1/decisions/dec_abc123def456",
            json=MOCK_DECISION,
        )
        async with AsyncNexure(api_key="test_key", base_url="http://test:8000/v1") as nx:
            result = await nx.decisions.get("dec_abc123def456")
        assert isinstance(result, DecisionRecord)
        assert result.id == "dec_abc123def456"

    @pytest.mark.asyncio
    async def test_list(self, httpx_mock: HTTPXMock):
        httpx_mock.add_response(
            method="GET",
            url=httpx.URL(
                "http://test:8000/v1/decisions",
                params={"limit": "50", "offset": "0"},
            ),
            json={
                "decisions": [MOCK_DECISION],
                "total": 1,
                "page": 1,
                "page_size": 50,
                "has_more": False,
                "limit": 50,
                "offset": 0,
            },
        )
        async with AsyncNexure(api_key="test_key", base_url="http://test:8000/v1") as nx:
            result = await nx.decisions.list()
        assert isinstance(result, DecisionList)
        assert result.total == 1

    @pytest.mark.asyncio
    async def test_forensics(self, httpx_mock: HTTPXMock):
        httpx_mock.add_response(
            method="GET",
            url="http://test:8000/v1/decisions/dec_abc123def456/forensics",
            json={
                "record": MOCK_DECISION,
                "package": MOCK_PACKAGE,
                "brief": None,
                "chain_verified": True,
                "chain_position": 1,
                "chain_length": 10,
            },
        )
        async with AsyncNexure(api_key="test_key", base_url="http://test:8000/v1") as nx:
            result = await nx.decisions.forensics("dec_abc123def456")
        assert isinstance(result, ForensicReport)
        assert result.chain_verified is True
        assert result.chain_position == 1


# ── Packages ─────────────────────────────────────────────────────


class TestAsyncPackages:
    @pytest.mark.asyncio
    async def test_create(self, httpx_mock: HTTPXMock):
        httpx_mock.add_response(
            method="POST",
            url="http://test:8000/v1/packages",
            json=MOCK_PACKAGE,
            status_code=201,
        )
        async with AsyncNexure(api_key="test_key", base_url="http://test:8000/v1") as nx:
            result = await nx.packages.create(
                decision_type="credit_underwriting",
                evidence={"fico_score": 711},
            )
        assert isinstance(result, DecisionPackage)
        assert result.id == "pkg_abc123def456"

    @pytest.mark.asyncio
    async def test_get(self, httpx_mock: HTTPXMock):
        httpx_mock.add_response(
            method="GET",
            url="http://test:8000/v1/packages/pkg_abc123def456",
            json=MOCK_PACKAGE,
        )
        async with AsyncNexure(api_key="test_key", base_url="http://test:8000/v1") as nx:
            result = await nx.packages.get("pkg_abc123def456")
        assert isinstance(result, DecisionPackage)
        assert result.id == "pkg_abc123def456"


# ── Briefs ───────────────────────────────────────────────────────


class TestAsyncBriefs:
    @pytest.mark.asyncio
    async def test_generate(self, httpx_mock: HTTPXMock):
        httpx_mock.add_response(
            method="POST",
            url="http://test:8000/v1/briefs/generate",
            json=MOCK_BRIEF,
            status_code=201,
        )
        async with AsyncNexure(api_key="test_key", base_url="http://test:8000/v1") as nx:
            result = await nx.briefs.generate(
                decision_type="credit_underwriting",
                evidence={"fico_score": 711, "dti_ratio": 0.38},
            )
        assert isinstance(result, DecisionBrief)
        assert result.id == "brf_abc123def456"
        assert result.quality_score == 72

    @pytest.mark.asyncio
    async def test_get(self, httpx_mock: HTTPXMock):
        httpx_mock.add_response(
            method="GET",
            url="http://test:8000/v1/briefs/brf_abc123def456",
            json=MOCK_BRIEF,
        )
        async with AsyncNexure(api_key="test_key", base_url="http://test:8000/v1") as nx:
            result = await nx.briefs.get("brf_abc123def456")
        assert isinstance(result, DecisionBrief)
        assert result.template_id == "credit_underwriting"


# ── Analytics ────────────────────────────────────────────────────


class TestAsyncAnalytics:
    @pytest.mark.asyncio
    async def test_overview(self, httpx_mock: HTTPXMock):
        httpx_mock.add_response(
            method="GET",
            url="http://test:8000/v1/analytics/overview?days=30",
            json=MOCK_OVERVIEW,
        )
        async with AsyncNexure(api_key="test_key", base_url="http://test:8000/v1") as nx:
            result = await nx.analytics.overview()
        assert isinstance(result, AnalyticsOverview)
        assert result.total_decisions == 4000
        assert len(result.decisions_by_day) == 1

    @pytest.mark.asyncio
    async def test_override_rates(self, httpx_mock: HTTPXMock):
        httpx_mock.add_response(
            method="GET",
            url="http://test:8000/v1/analytics/overrides?lookback_days=30",
            json=MOCK_OVERRIDES,
        )
        async with AsyncNexure(api_key="test_key", base_url="http://test:8000/v1") as nx:
            result = await nx.analytics.override_rates()
        assert isinstance(result, AnalyticsOverrides)
        assert result.reviewers[0].is_anomaly is True
        assert result.reviewers[0].override_direction is not None

    @pytest.mark.asyncio
    async def test_drift(self, httpx_mock: HTTPXMock):
        httpx_mock.add_response(
            method="GET",
            url="http://test:8000/v1/analytics/drift?weeks=12",
            json=MOCK_DRIFT,
        )
        async with AsyncNexure(api_key="test_key", base_url="http://test:8000/v1") as nx:
            result = await nx.analytics.drift()
        assert isinstance(result, AnalyticsDrift)
        assert len(result.weeks) == 1
        assert result.alerts[0].severity == "low"

    @pytest.mark.asyncio
    async def test_comparisons(self, httpx_mock: HTTPXMock):
        httpx_mock.add_response(
            method="GET",
            url="http://test:8000/v1/analytics/comparisons?limit=20&min_similarity=0.7",
            json=MOCK_COMPARISONS,
        )
        async with AsyncNexure(api_key="test_key", base_url="http://test:8000/v1") as nx:
            result = await nx.analytics.comparisons()
        assert isinstance(result, AnalyticsComparisons)
        assert len(result.pairs) == 1
        assert result.pairs[0].key_differences == []

    @pytest.mark.asyncio
    async def test_visibility_score(self, httpx_mock: HTTPXMock):
        httpx_mock.add_response(
            method="GET",
            url="http://test:8000/v1/analytics/visibility-score",
            json=MOCK_VISIBILITY,
        )
        async with AsyncNexure(api_key="test_key", base_url="http://test:8000/v1") as nx:
            result = await nx.analytics.visibility_score()
        assert isinstance(result, VisibilityScore)
        assert result.score == 67.3
        assert len(result.components) == 1


# ── Templates ────────────────────────────────────────────────────


class TestAsyncTemplates:
    @pytest.mark.asyncio
    async def test_list(self, httpx_mock: HTTPXMock):
        httpx_mock.add_response(
            method="GET",
            url="http://test:8000/v1/templates",
            json={"templates": [MOCK_TEMPLATE]},
        )
        async with AsyncNexure(api_key="test_key", base_url="http://test:8000/v1") as nx:
            result = await nx.templates.list()
        assert isinstance(result, TemplateList)
        assert len(result.templates) == 1

    @pytest.mark.asyncio
    async def test_get(self, httpx_mock: HTTPXMock):
        httpx_mock.add_response(
            method="GET",
            url="http://test:8000/v1/templates/credit_underwriting",
            json={**MOCK_TEMPLATE, "config": {"template": {"id": "credit_underwriting"}}},
        )
        async with AsyncNexure(api_key="test_key", base_url="http://test:8000/v1") as nx:
            result = await nx.templates.get("credit_underwriting")
        assert isinstance(result, TemplateDetail)
        assert result.id == "credit_underwriting"
        assert "template" in result.config

    @pytest.mark.asyncio
    async def test_register(self, httpx_mock: HTTPXMock):
        httpx_mock.add_response(
            method="POST",
            url="http://test:8000/v1/templates",
            json=MOCK_TEMPLATE,
            status_code=201,
        )
        async with AsyncNexure(api_key="test_key", base_url="http://test:8000/v1") as nx:
            result = await nx.templates.register(template_yaml="template:\n  id: test")
        assert isinstance(result, TemplateInfo)
        assert result.id == "credit_underwriting"

    @pytest.mark.asyncio
    async def test_update(self, httpx_mock: HTTPXMock):
        updated = {**MOCK_TEMPLATE, "version": "1.1.0"}
        httpx_mock.add_response(
            method="PUT",
            url="http://test:8000/v1/templates/credit_underwriting",
            json=updated,
        )
        async with AsyncNexure(api_key="test_key", base_url="http://test:8000/v1") as nx:
            result = await nx.templates.update(
                "credit_underwriting", template_yaml="template:\n  id: credit_underwriting"
            )
        assert isinstance(result, TemplateInfo)
        assert result.version == "1.1.0"


# ── Ledger ───────────────────────────────────────────────────────


class TestAsyncLedger:
    @pytest.mark.asyncio
    async def test_verify(self, httpx_mock: HTTPXMock):
        httpx_mock.add_response(
            method="GET",
            url="http://test:8000/v1/ledger/verify",
            json=MOCK_LEDGER,
        )
        async with AsyncNexure(api_key="test_key", base_url="http://test:8000/v1") as nx:
            result = await nx.ledger.verify()
        assert isinstance(result, LedgerVerification)
        assert result.valid is True
        assert result.records_checked == 500
