"""
Integration tests — round-trip against the live Oracle.

These tests fetch real receipts from headlessoracle.com and verify them
using the Python SDK. This is the CI compatibility gate that catches
schema drift between the deployed worker and the SDK.

Run: python3 -m pytest tests/test_integration.py -v -m integration
Or:  python3 -m pytest tests/ -v   (runs all tests including integration)

Requirements: live internet access to headlessoracle.com
"""

import pytest
import httpx

from headless_oracle.verify import verify
from headless_oracle.client import OracleClient, OracleError

# Production public key — the Ed25519 key that headlessoracle.com signs with.
# This is public information; it is served at /v5/keys and /.well-known/oracle-keys.json.
ORACLE_PUBLIC_KEY = "03dc27993a2c90856cdeb45e228ac065f18f69f0933c917b2336c1e75712f178"
ORACLE_BASE       = "https://headlessoracle.com"

ALL_MICS = ["XNYS", "XNAS", "XLON", "XJPX", "XPAR", "XHKG", "XSES"]


pytestmark = pytest.mark.integration


# ── OracleClient + verify() round-trip ───────────────────────────────────────

class TestDemoRoundTrip:
    """Fetch /v5/demo and verify the Ed25519 signature using the Python SDK."""

    def test_xnys_demo_verifies(self):
        with OracleClient(base_url=ORACLE_BASE) as client:
            receipt = client.get_demo("XNYS")

        result = verify(receipt, public_key=ORACLE_PUBLIC_KEY)
        assert result.valid is True, f"Verification failed: {result.reason}"

    def test_demo_status_is_known_value(self):
        with OracleClient(base_url=ORACLE_BASE) as client:
            receipt = client.get_demo("XNYS")

        assert receipt["status"] in {"OPEN", "CLOSED", "HALTED", "UNKNOWN"}

    def test_demo_receipt_has_issuer_field(self):
        """Regression: issuer field added Mar 3 2026 — must be present and signed."""
        with OracleClient(base_url=ORACLE_BASE) as client:
            receipt = client.get_demo("XNYS")

        assert receipt.get("issuer") == "headlessoracle.com", (
            f"issuer field missing or wrong: {receipt.get('issuer')}"
        )
        # Verify that issuer is actually part of the signed payload
        # (a tampered issuer would cause INVALID_SIGNATURE)
        result = verify(receipt, public_key=ORACLE_PUBLIC_KEY)
        assert result.valid is True

    def test_demo_receipt_mode_is_demo(self):
        with OracleClient(base_url=ORACLE_BASE) as client:
            receipt = client.get_demo("XNYS")

        assert receipt["receipt_mode"] == "demo"

    def test_demo_schema_version(self):
        with OracleClient(base_url=ORACLE_BASE) as client:
            receipt = client.get_demo("XNYS")

        assert receipt["schema_version"] == "v5.0"

    def test_all_required_fields_present(self):
        required = {
            "receipt_id", "issued_at", "expires_at", "issuer", "mic",
            "status", "source", "receipt_mode", "schema_version",
            "public_key_id", "signature",
        }
        with OracleClient(base_url=ORACLE_BASE) as client:
            receipt = client.get_demo("XNYS")

        missing = required - set(receipt.keys())
        assert not missing, f"Missing fields: {missing}"

    @pytest.mark.parametrize("mic", ALL_MICS)
    def test_all_mics_verify(self, mic):
        """Every supported MIC returns a valid signed receipt."""
        with OracleClient(base_url=ORACLE_BASE) as client:
            receipt = client.get_demo(mic)

        result = verify(receipt, public_key=ORACLE_PUBLIC_KEY)
        assert result.valid is True, f"{mic}: verification failed: {result.reason}"
        assert receipt["mic"] == mic


class TestPublicKeyDiscovery:
    """Verify that the SDK can discover and use the public key from the key registry."""

    def test_verify_without_public_key_arg(self):
        """
        verify() with no public_key= fetches the key from {issuer}/v5/keys.
        This tests the full self-describing receipt flow: issuer → /v5/keys → verify.
        """
        with OracleClient(base_url=ORACLE_BASE) as client:
            receipt = client.get_demo("XNYS")

        # No public_key= argument — SDK must resolve issuer and fetch the key
        result = verify(receipt, keys_url=f"{ORACLE_BASE}/v5/keys")
        assert result.valid is True, f"Key-discovery verify failed: {result.reason}"

    def test_well_known_key_matches_keys_endpoint(self):
        """/.well-known/oracle-keys.json and /v5/keys must serve the same public key."""
        keys_resp      = httpx.get(f"{ORACLE_BASE}/v5/keys", timeout=10).json()
        well_known     = httpx.get(f"{ORACLE_BASE}/.well-known/oracle-keys.json", timeout=10).json()

        keys_pubkeys      = {k["key_id"]: k["public_key"] for k in keys_resp.get("keys", [])}
        well_known_pubkeys = {k["key_id"]: k["public_key"] for k in well_known.get("keys", [])}

        assert keys_pubkeys == well_known_pubkeys, (
            "Public keys differ between /v5/keys and /.well-known/oracle-keys.json"
        )

    def test_production_public_key_is_correct(self):
        """The hardcoded ORACLE_PUBLIC_KEY constant matches what the server reports."""
        resp = httpx.get(f"{ORACLE_BASE}/v5/keys", timeout=10).json()
        server_keys = {k["public_key"] for k in resp.get("keys", [])}
        assert ORACLE_PUBLIC_KEY in server_keys, (
            f"Hardcoded ORACLE_PUBLIC_KEY not found in /v5/keys. "
            f"Server keys: {server_keys}"
        )


class TestHealthRoundTrip:
    """Health receipt is a different schema — no mic, no schema_version."""

    def test_health_verifies(self):
        with OracleClient(base_url=ORACLE_BASE) as client:
            health = client.get_health()

        # Health data is in the top-level response alongside the signed receipt
        # The signed part is the receipt itself
        result = verify(health, public_key=ORACLE_PUBLIC_KEY)
        assert result.valid is True, f"Health verify failed: {result.reason}"

    def test_health_has_issuer(self):
        with OracleClient(base_url=ORACLE_BASE) as client:
            health = client.get_health()

        assert health.get("issuer") == "headlessoracle.com"

    def test_health_no_mic_field(self):
        with OracleClient(base_url=ORACLE_BASE) as client:
            health = client.get_health()

        assert "mic" not in health, "Health receipt must not contain a mic field"

    def test_health_status_ok(self):
        with OracleClient(base_url=ORACLE_BASE) as client:
            health = client.get_health()

        assert health["status"] == "OK"

    def test_health_has_exchange_count(self):
        with OracleClient(base_url=ORACLE_BASE) as client:
            health = client.get_health()

        assert health.get("exchange_count") == 7


class TestScheduleEndpoint:
    """Schedule endpoint — no signing, but structural contract."""

    def test_schedule_returns_expected_fields(self):
        with OracleClient(base_url=ORACLE_BASE) as client:
            sched = client.get_schedule("XNYS")

        assert sched["mic"] == "XNYS"
        assert "next_open" in sched
        assert "next_close" in sched
        assert "timezone" in sched
        assert "data_coverage_years" in sched
        assert isinstance(sched["data_coverage_years"], list)
        assert len(sched["data_coverage_years"]) >= 1

    def test_schedule_lunch_break_xjpx(self):
        with OracleClient(base_url=ORACLE_BASE) as client:
            sched = client.get_schedule("XJPX")

        lb = sched.get("lunch_break")
        assert lb is not None, "XJPX must have a lunch_break"
        assert lb["start"] == "11:30"
        assert lb["end"] == "12:30"

    def test_schedule_no_lunch_break_xnys(self):
        with OracleClient(base_url=ORACLE_BASE) as client:
            sched = client.get_schedule("XNYS")

        assert sched["lunch_break"] is None, "XNYS must have lunch_break: null"


class TestInvalidMic:
    """Error handling — bad MIC codes return 400."""

    def test_invalid_mic_raises_oracle_error(self):
        with OracleClient(base_url=ORACLE_BASE) as client:
            with pytest.raises(OracleError) as exc_info:
                client.get_demo("INVALID")

        assert exc_info.value.status_code == 400
