"""Tests for Orxaq Starlette REST API."""

from __future__ import annotations

import json

import pytest
from starlette.testclient import TestClient

from orxaq.enterprise.auth import APIKeyStore, AuthMiddleware
from orxaq.experiences.openapi import generate_spec
from orxaq.experiences.rest_api import make_app
from orxaq.experiences.schemas import (
    ECLComputeRequest,
    ECLComputeResponse,
    FilingGenerateRequest,
    FilingGenerateResponse,
    RegulatoryExportRequest,
    RegulatoryExportResponse,
    ScenarioCompareRequest,
    ScenarioCompareResponse,
    StressTestRequest,
    StressTestResponse,
)


@pytest.fixture(scope="module")
def client_open():
    return TestClient(make_app())


@pytest.fixture(scope="module")
def authed_client():
    store = APIKeyStore()
    raw_key, _ = store.generate_key("test-key")
    middleware = AuthMiddleware(key_store=store)
    client = TestClient(make_app(auth_middleware=middleware))
    return client, raw_key


class TestHealthEndpoint:
    def test_200(self, client_open):
        assert client_open.get("/api/v1/health").status_code == 200

    def test_status_ok(self, client_open):
        assert client_open.get("/api/v1/health").json()["status"] == "ok"

    def test_has_version(self, client_open):
        assert "version" in client_open.get("/api/v1/health").json()

    def test_has_timestamp(self, client_open):
        assert "T" in client_open.get("/api/v1/health").json()["timestamp"]


class TestAuthGate:
    def test_ontology_401(self, authed_client):
        client, _ = authed_client
        assert client.get("/api/v1/ontology").status_code == 401

    def test_ecl_401(self, authed_client):
        client, _ = authed_client
        assert client.post("/api/v1/ecl/compute", json={}).status_code == 401

    def test_filing_401(self, authed_client):
        client, _ = authed_client
        assert client.post("/api/v1/filing/generate", json={}).status_code == 401

    def test_stress_401(self, authed_client):
        client, _ = authed_client
        assert client.post("/api/v1/stress-test/run", json={}).status_code == 401

    def test_compare_401(self, authed_client):
        client, _ = authed_client
        assert client.post("/api/v1/scenarios/compare", json={}).status_code == 401

    def test_export_401(self, authed_client):
        client, _ = authed_client
        assert client.post("/api/v1/regulatory/export", json={}).status_code == 401

    def test_health_no_auth(self, authed_client):
        client, _ = authed_client
        assert client.get("/api/v1/health").status_code == 200

    def test_openapi_no_auth(self, authed_client):
        client, _ = authed_client
        assert client.get("/openapi.json").status_code == 200


class TestOntology:
    def test_200(self, client_open):
        assert client_open.get("/api/v1/ontology").status_code == 200

    def test_entities(self, client_open):
        assert "entities" in client_open.get("/api/v1/ontology").json()

    def test_edges(self, client_open):
        assert "edges" in client_open.get("/api/v1/ontology").json()

    def test_content_type(self, client_open):
        assert "application/json" in client_open.get("/api/v1/ontology").headers["content-type"]


class TestECLCompute:
    def test_200(self, client_open):
        assert (
            client_open.post("/api/v1/ecl/compute", json={"portfolio_size": 50}).status_code == 200
        )

    def test_ecl_float(self, client_open):
        assert isinstance(
            client_open.post("/api/v1/ecl/compute", json={"portfolio_size": 50}).json()["ecl"],
            float,
        )

    def test_confidence_ordered(self, client_open):
        d = client_open.post("/api/v1/ecl/compute", json={"portfolio_size": 50}).json()
        assert d["confidence_band"][0] <= d["confidence_band"][1]

    def test_zero_portfolio_400(self, client_open):
        assert (
            client_open.post("/api/v1/ecl/compute", json={"portfolio_size": 0}).status_code == 400
        )


class TestFilingGenerate:
    def test_200(self, client_open):
        assert (
            client_open.post(
                "/api/v1/filing/generate", json={"n_samples": 50, "horizon_quarters": 4}
            ).status_code
            == 200
        )

    def test_has_output_dir(self, client_open):
        assert (
            "output_dir"
            in client_open.post(
                "/api/v1/filing/generate", json={"n_samples": 50, "horizon_quarters": 4}
            ).json()
        )

    def test_files_list(self, client_open):
        assert (
            len(
                client_open.post(
                    "/api/v1/filing/generate", json={"n_samples": 50, "horizon_quarters": 4}
                ).json()["files"]
            )
            > 0
        )


class TestStressTestRun:
    def test_200(self, client_open):
        assert (
            client_open.post(
                "/api/v1/stress-test/run", json={"scenario": "adverse", "portfolio_size": 30}
            ).status_code
            == 200
        )

    def test_quarterly_ecl(self, client_open):
        assert isinstance(
            client_open.post(
                "/api/v1/stress-test/run", json={"scenario": "adverse", "portfolio_size": 30}
            ).json()["quarterly_ecl"],
            list,
        )

    def test_peak_quarter(self, client_open):
        assert (
            client_open.post(
                "/api/v1/stress-test/run", json={"scenario": "adverse", "portfolio_size": 30}
            )
            .json()["peak_quarter"]
            .startswith("Q")
        )

    def test_unknown_400(self, client_open):
        assert (
            client_open.post("/api/v1/stress-test/run", json={"scenario": "doomsday"}).status_code
            == 400
        )


class TestScenariosCompare:
    def test_200(self, client_open):
        assert (
            client_open.post("/api/v1/scenarios/compare", json={"portfolio_size": 30}).status_code
            == 200
        )

    def test_waterfall(self, client_open):
        assert (
            "capital_waterfall"
            in client_open.post("/api/v1/scenarios/compare", json={"portfolio_size": 30}).json()
        )

    def test_attribution(self, client_open):
        assert (
            "sensitivity_attribution"
            in client_open.post("/api/v1/scenarios/compare", json={"portfolio_size": 30}).json()
        )

    def test_unknown_400(self, client_open):
        assert (
            client_open.post(
                "/api/v1/scenarios/compare", json={"scenarios": ["doomsday"]}
            ).status_code
            == 400
        )


class TestRegulatoryExport:
    def test_cecl_200(self, client_open):
        assert (
            client_open.post("/api/v1/regulatory/export", json={"framework": "cecl"}).status_code
            == 200
        )

    def test_ccar_200(self, client_open):
        assert (
            client_open.post("/api/v1/regulatory/export", json={"framework": "ccar"}).status_code
            == 200
        )

    def test_framework_in_response(self, client_open):
        assert (
            client_open.post("/api/v1/regulatory/export", json={"framework": "cecl"}).json()[
                "framework"
            ]
            == "cecl"
        )


class TestOpenAPI:
    def test_200(self, client_open):
        assert client_open.get("/openapi.json").status_code == 200

    def test_valid_json(self, client_open):
        assert isinstance(client_open.get("/openapi.json").json(), dict)

    def test_openapi_key(self, client_open):
        assert "openapi" in client_open.get("/openapi.json").json()

    def test_paths(self, client_open):
        assert "paths" in client_open.get("/openapi.json").json()

    def test_all_6_endpoints(self, client_open):
        paths = client_open.get("/openapi.json").json()["paths"]
        assert {
            "/api/v1/ontology",
            "/api/v1/ecl/compute",
            "/api/v1/filing/generate",
            "/api/v1/stress-test/run",
            "/api/v1/scenarios/compare",
            "/api/v1/regulatory/export",
        }.issubset(set(paths.keys()))


class TestSchemas:
    def test_ecl_req_rt(self):
        req = ECLComputeRequest(portfolio_size=100, scenario="adverse")
        assert ECLComputeRequest.from_dict(req.to_dict()).portfolio_size == 100

    def test_ecl_resp_rt(self):
        r = ECLComputeResponse(ecl=0.05, confidence_band=[0.04, 0.06], segments={})
        assert ECLComputeResponse.from_dict(r.to_dict()).ecl == 0.05

    def test_filing_req_rt(self):
        r = FilingGenerateRequest(institution_id="MYBANK")
        assert FilingGenerateRequest.from_dict(r.to_dict()).institution_id == "MYBANK"

    def test_filing_resp_rt(self):
        r = FilingGenerateResponse(
            output_dir="/tmp", files=["a.json"], audit_entries=5, duration_ms=1.0
        )
        assert FilingGenerateResponse.from_dict(r.to_dict()).audit_entries == 5

    def test_stress_req_rt(self):
        r = StressTestRequest(scenario="severely_adverse", horizon=4)
        assert StressTestRequest.from_dict(r.to_dict()).horizon == 4

    def test_stress_resp_rt(self):
        r = StressTestResponse(scenario="adverse", quarterly_ecl=[0.1, 0.2], peak_quarter="Q2")
        assert StressTestResponse.from_dict(r.to_dict()).peak_quarter == "Q2"

    def test_compare_req_rt(self):
        r = ScenarioCompareRequest(scenarios=["baseline", "adverse"], cet1_ratio=0.15)
        r2 = ScenarioCompareRequest.from_dict(r.to_dict())
        assert r2.cet1_ratio == 0.15 and len(r2.scenarios) == 2

    def test_compare_resp_rt(self):
        r = ScenarioCompareResponse(
            ecl_deltas={"x": [0.0]},
            capital_waterfall={},
            sensitivity_attribution={},
            peak_loss_quarter={},
        )
        assert "x" in ScenarioCompareResponse.from_dict(r.to_dict()).ecl_deltas

    def test_reg_req_rt(self):
        r = RegulatoryExportRequest(framework="ccar", period="2025-Q1")
        assert RegulatoryExportRequest.from_dict(r.to_dict()).period == "2025-Q1"

    def test_reg_resp_rt(self):
        r = RegulatoryExportResponse(framework="cecl", content={"k": "v"})
        assert RegulatoryExportResponse.from_dict(r.to_dict()).framework == "cecl"


class TestGenerateSpec:
    def test_is_dict(self):
        assert isinstance(generate_spec(), dict)

    def test_openapi_31(self):
        assert generate_spec()["openapi"].startswith("3.1")

    def test_all_6_paths(self):
        paths = generate_spec()["paths"]
        assert {
            "/api/v1/ontology",
            "/api/v1/ecl/compute",
            "/api/v1/filing/generate",
            "/api/v1/stress-test/run",
            "/api/v1/scenarios/compare",
            "/api/v1/regulatory/export",
        }.issubset(set(paths.keys()))

    def test_json_serializable(self):
        assert isinstance(json.dumps(generate_spec()), str)

    def test_security_schemes(self):
        assert "securitySchemes" in generate_spec()["components"]
