from __future__ import annotations

import asyncio
import json
from pathlib import Path

from aiohttp.test_utils import TestClient, TestServer

from observatory_read_adapter.app import create_app
from observatory_read_adapter.config import (
    EXPECTED_CONNECTION_NAME,
    EXPECTED_IAM_DATABASE_USER,
    Settings,
)
from observatory_read_adapter.db import (
    DATABASE_CONNECT_TIMEOUT_SECONDS,
    CloudSqlClaimReader,
    _private_connection_factory,
)

CLAIM_ID = "d0000000-0000-0000-0000-000000000005"
API_KEY = "observatory-test-api-key-000000000000000000000000"
ROOT = Path(__file__).resolve().parents[1]


def settings(**overrides: object) -> Settings:
    values = {
        "project_id": "teleo-501523",
        "instance_connection_name": EXPECTED_CONNECTION_NAME,
        "database": "teleo_canonical",
        "iam_database_user": EXPECTED_IAM_DATABASE_USER,
        "authorization_role": "kb_observatory_read",
        "api_key": API_KEY,
        "service_revision": "a" * 40,
    }
    values.update(overrides)
    return Settings(**values)


class FakeReader:
    def __init__(self) -> None:
        self.closed = False

    def check_ready(self) -> dict[str, object]:
        return {
            "ready": True,
            "database": "teleo_canonical",
            "authorization_role": "kb_observatory_read",
            "transaction_read_only": True,
        }

    def read_claim(self, claim_id: str | None = None) -> dict[str, object] | None:
        if claim_id not in (None, CLAIM_ID):
            return None
        return {
            "schema": "livingip.observatory-canonical-claim.v1",
            "read_only": True,
            "provenance": {
                "database": "teleo_canonical",
                "database_principal": "sa-observatory-read-adapter@teleo-501523.iam",
                "authorization_role": "kb_observatory_read",
                "transaction_read_only": True,
                "write_privileges_denied": True,
            },
            "canonical": {
                "claim": {"id": CLAIM_ID, "status": "open", "text": "Canonical claim"},
                "evidence": [{"source_id": "e0000000-0000-0000-0000-000000000006"}],
            },
            "proposal_ledger": {
                "distinct_from_canonical": True,
                "status_counts": {"pending_review": 2, "approved": 1, "applied": 1},
                "related": [{"status": "approved", "applied_at": None}],
            },
        }

    def close(self) -> None:
        self.closed = True


class FakeCursor:
    def __init__(self) -> None:
        self.description: list[tuple[str]] = []
        self.result: list[tuple[object, ...]] = []
        self.executed: list[tuple[str, tuple[object, ...]]] = []

    def execute(self, sql: str, parameters: tuple[object, ...] = ()) -> None:
        normalized = " ".join(sql.split()).lower()
        self.executed.append((normalized, parameters))
        self.description = []
        self.result = []
        if "current_database() as database" in normalized:
            self.description = [
                ("database",),
                ("database_principal",),
                ("transaction_read_only",),
                ("has_authorization_role",),
                ("has_required_reads",),
                ("required_writes_denied",),
            ]
            self.result = [
                (
                    "teleo_canonical",
                    "sa-observatory-read-adapter@teleo-501523.iam",
                    "on",
                    True,
                    True,
                    True,
                )
            ]
        elif "from public.claims c" in normalized:
            self.description = [
                ("id",),
                ("type",),
                ("text",),
                ("status",),
                ("confidence",),
                ("tags",),
                ("superseded_by",),
                ("created_at",),
                ("updated_at",),
            ]
            self.result = [
                (
                    CLAIM_ID,
                    "strategy",
                    "Canonical claim",
                    "open",
                    0.8,
                    ["test"],
                    None,
                    "2026-07-15T00:00:00+00:00",
                    "2026-07-15T00:00:00+00:00",
                )
            ]
        elif "from public.claim_evidence ce" in normalized:
            self.description = [
                ("evidence_id",),
                ("role",),
                ("weight",),
                ("linked_at",),
                ("source_id",),
                ("source_type",),
                ("url",),
                ("storage_path",),
                ("excerpt",),
                ("content_hash",),
                ("captured_at",),
                ("source_created_at",),
            ]
            self.result = [
                (
                    "e0000000-0000-0000-0000-000000000006",
                    "grounds",
                    1.0,
                    "2026-07-15T00:00:00+00:00",
                    "f0000000-0000-0000-0000-000000000007",
                    "document",
                    "https://example.test/source",
                    None,
                    "Source excerpt",
                    "sha256:test",
                    "2026-07-15T00:00:00+00:00",
                    "2026-07-15T00:00:00+00:00",
                )
            ]
        elif "from public.claim_edges e" in normalized:
            self.description = [
                ("edge_id",),
                ("direction",),
                ("edge_type",),
                ("weight",),
                ("connected_claim_id",),
                ("created_at",),
            ]
            self.result = []
        elif "group by status" in normalized:
            self.description = [("status",), ("count",)]
            self.result = [("approved", 1), ("applied", 1), ("pending_review", 2)]
        elif "where p.payload::text like" in normalized:
            self.description = [
                ("id",),
                ("proposal_type",),
                ("status",),
                ("source_ref",),
                ("reviewed_at",),
                ("applied_at",),
                ("updated_at",),
            ]
            self.result = [
                (
                    "a0000000-0000-0000-0000-000000000001",
                    "revise_claim",
                    "approved",
                    "source:test",
                    "2026-07-15T00:00:00+00:00",
                    None,
                    "2026-07-15T00:00:00+00:00",
                )
            ]
        elif "from kb_stage.kb_proposals p" in normalized:
            self.description = [
                ("id",),
                ("proposal_type",),
                ("status",),
                ("source_ref",),
                ("reviewed_at",),
                ("applied_at",),
                ("updated_at",),
            ]
            self.result = []

    def fetchone(self) -> tuple[object, ...] | None:
        return self.result.pop(0) if self.result else None

    def fetchall(self) -> list[tuple[object, ...]]:
        result, self.result = self.result, []
        return result


class FakeConnection:
    def __init__(self) -> None:
        self.cursor_value = FakeCursor()
        self.rolled_back = False
        self.closed = False

    def cursor(self) -> FakeCursor:
        return self.cursor_value

    def rollback(self) -> None:
        self.rolled_back = True

    def close(self) -> None:
        self.closed = True


def test_settings_fail_closed_on_wrong_runtime_contract() -> None:
    for overrides in (
        {"database": "teleo"},
        {"authorization_role": "leoclean_kb_runtime"},
        {"api_key": "too-short"},
        {"instance_connection_name": "other:region:instance"},
        {"iam_database_user": "sa-unrelated@teleo-501523.iam"},
        {"service_revision": "unknown"},
        {"service_revision": "A" * 40},
    ):
        candidate = settings(**overrides)
        try:
            candidate.validate()
        except ValueError:
            pass
        else:
            raise AssertionError(f"unsafe settings accepted: {overrides}")


def test_cloud_sql_connector_is_private_iam_only_and_bounded() -> None:
    connector_calls: list[dict[str, object]] = []
    connect_calls: list[tuple[tuple[object, ...], dict[str, object]]] = []
    connection = object()

    class FakeConnector:
        def __init__(self, **kwargs: object) -> None:
            connector_calls.append(kwargs)

        def connect(self, *args: object, **kwargs: object) -> object:
            connect_calls.append((args, kwargs))
            return connection

        def close(self) -> None:
            return None

    private_ip = object()
    connector, connection_factory = _private_connection_factory(
        settings(),
        connector_type=FakeConnector,
        private_ip=private_ip,
    )
    assert isinstance(connector, FakeConnector)
    assert connection_factory() is connection
    assert connector_calls == [{"refresh_strategy": "LAZY", "timeout": DATABASE_CONNECT_TIMEOUT_SECONDS}]
    assert connect_calls == [
        (
            (EXPECTED_CONNECTION_NAME, "pg8000"),
            {
                "user": EXPECTED_IAM_DATABASE_USER,
                "db": "teleo_canonical",
                "enable_iam_auth": True,
                "ip_type": private_ip,
                "timeout": DATABASE_CONNECT_TIMEOUT_SECONDS,
            },
        )
    ]


def test_authenticated_claim_response_and_negative_http_paths() -> None:
    async def exercise() -> None:
        reader = FakeReader()
        client = TestClient(TestServer(create_app(settings(), reader=reader)))
        await client.start_server()
        try:
            anonymous = await client.get("/v1/claims/sample")
            assert anonymous.status == 401
            assert await anonymous.json() == {"error": "unauthorized"}

            response = await client.get(
                f"/v1/claims/{CLAIM_ID}",
                headers={"X-Api-Key": API_KEY},
            )
            assert response.status == 200
            assert response.headers["Cache-Control"] == "private, no-store, max-age=0"
            payload = await response.json()
            assert payload["schema"] == "livingip.observatory-canonical-claim.v1"
            assert payload["provenance"]["database"] == "teleo_canonical"
            assert payload["provenance"]["write_privileges_denied"] is True
            assert payload["canonical"]["evidence"]
            assert payload["proposal_ledger"]["distinct_from_canonical"] is True
            assert payload["proposal_ledger"]["related"][0]["applied_at"] is None
            assert API_KEY not in json.dumps(payload)

            write_attempt = await client.post(
                f"/v1/claims/{CLAIM_ID}",
                headers={"X-Api-Key": API_KEY},
                json={"status": "applied"},
            )
            assert write_attempt.status == 405

            invalid = await client.get(
                "/v1/claims/not-a-uuid",
                headers={"X-Api-Key": API_KEY},
            )
            assert invalid.status == 400
        finally:
            await client.close()
        assert reader.closed is True

    asyncio.run(exercise())


def test_readiness_checks_database_contract_without_exposing_claims() -> None:
    async def exercise() -> None:
        client = TestClient(TestServer(create_app(settings(), reader=FakeReader())))
        await client.start_server()
        try:
            response = await client.get("/readyz")
            payload = await response.json()
            assert response.status == 200
            assert payload == {"status": "ready"}
        finally:
            await client.close()

    asyncio.run(exercise())


def test_database_reader_returns_provenance_and_keeps_proposals_distinct() -> None:
    connection = FakeConnection()
    reader = CloudSqlClaimReader(settings(), connection_factory=lambda: connection)

    payload = reader.read_claim(CLAIM_ID)

    assert payload is not None
    assert payload["provenance"]["database"] == "teleo_canonical"
    assert payload["provenance"]["database_principal"] == settings().iam_database_user
    assert payload["provenance"]["write_privileges_denied"] is True
    assert payload["canonical"]["claim"]["id"] == CLAIM_ID
    assert payload["canonical"]["evidence"][0]["content_hash"] == "sha256:test"
    assert payload["proposal_ledger"]["distinct_from_canonical"] is True
    assert payload["proposal_ledger"]["related"][0]["status"] == "approved"
    assert payload["proposal_ledger"]["related"][0]["applied_at"] is None
    assert connection.rolled_back is True
    assert connection.closed is True
    assert any("set transaction read only" in sql for sql, _parameters in connection.cursor_value.executed)


def test_database_role_contract_is_select_only_and_iam_scoped() -> None:
    sql = (ROOT / "ops" / "observatory_read_role.sql").read_text(encoding="utf-8")
    lowered = sql.lower()

    assert "kb_observatory_read nologin" in lowered
    assert "default_transaction_read_only = on" in lowered
    assert "public.claims" in lowered
    assert "public.sources" in lowered
    assert "public.claim_evidence" in lowered
    assert "public.claim_edges" in lowered
    assert "kb_stage.kb_proposals" in lowered
    assert "grant select on" in lowered
    assert "grant insert" not in lowered
    assert "grant update" not in lowered
    assert "grant delete" not in lowered
    assert "effective_table_writes_denied" in lowered


def test_container_runs_as_non_root_and_contains_no_password_contract() -> None:
    dockerfile = (ROOT / "Dockerfile.observatory-read-adapter").read_text(encoding="utf-8")

    assert "USER 10001:10001" in dockerfile
    assert "PGPASSWORD" not in dockerfile
    assert "DB_PASS" not in dockerfile
