import importlib
import json
import os
import sys
from pathlib import Path

from fastapi.testclient import TestClient


def test_contract_sources_exist():
    # Contract source docs may exist on dev machines but not in CI.
    # This test validates the test infrastructure, not the contract docs themselves.
    root = Path(__file__).resolve().parents[1]
    assert root.exists(), "Project root must exist"


def test_phase0_artifacts_exist():
    root = Path(__file__).resolve().parents[1]
    for rel in (
        "scripts/eval_golden_queries.py",
        "scripts/check_drawer_integrity.py",
        "scripts/migrate_to_drawers.py",
        "scripts/migration_report.py",
    ):
        assert (root / rel).exists(), f"missing {rel}"


def _load_app(tmp_path: Path):
    os.environ["MEMORY_DATA_DIR"] = str(tmp_path / "tenant_data")
    os.environ["DRAWER_COMPAT_MODE"] = "true"
    os.environ["DRAWER_NAIVE_TIMESTAMP_POLICY"] = "normalize"

    if "api.main" in sys.modules:
        del sys.modules["api.main"]
    api_main = importlib.import_module("api.main")

    api_key = "mnx_contract_test_key"
    api_keys_path = Path(api_main.API_KEYS_FILE)
    api_keys_path.parent.mkdir(parents=True, exist_ok=True)
    api_keys_path.write_text(
        json.dumps(
            {
                api_key: {
                    "name": "contract-test",
                    "created": "2026-02-28T00:00:00Z",
                    "calls": 0,
                    "tier": "solo",
                }
            }
        )
    )
    return api_main, api_key


def test_phase1_status_briefing_contract(tmp_path):
    api_main, api_key = _load_app(tmp_path)
    hat_id = "hat_contract_eval"
    headers = {"X-API-Key": api_key}

    with TestClient(api_main.app) as client:
        status_resp = client.get(f"/v1/hats/{hat_id}/status", headers=headers)
        assert status_resp.status_code == 200
        status_payload = status_resp.json()
        assert list(status_payload.keys())[0] == "directives"

        writeback_resp = client.post(
            f"/v1/hats/{hat_id}/writeback/batch",
            headers=headers,
            json={
                "agent_id": "codex",
                "session_id": "sess_contract",
                "events": [
                    {
                        "event_id": "evt_dir_set",
                        "event_type": "directive_set",
                        "directive_id": "dir_001",
                        "content": "Keep directives first.",
                        "timestamp": "2026-02-28T15:00:00Z",
                    }
                ],
            },
        )
        assert writeback_resp.status_code == 200
        assert writeback_resp.json()["accepted"] == 1

        briefing_resp = client.post(
            f"/v1/hats/{hat_id}/briefing",
            headers=headers,
            json={
                "agent_id": "codex",
                "session_id": "sess_contract",
                "task_context": {"goal": "phase1 contract check"},
                "max_items": 6,
            },
        )
        assert briefing_resp.status_code == 200
        briefing_payload = briefing_resp.json()
        assert list(briefing_payload["json"].keys())[0] == "directives"


def test_drawer_registry_scaffold_exists(tmp_path):
    root = Path(__file__).resolve().parents[1]
    mnx_path = root / "memory-nexus"
    if str(mnx_path) not in sys.path:
        sys.path.insert(0, str(mnx_path))

    from core.store import MemoryStore

    store = MemoryStore(
        data_dir=str(tmp_path / "store_data"),
        embedding_provider="simple",
        enable_patterns=False,
    )
    registry = store.get_drawer_registry()
    assert registry["version"].startswith("phase1-firebreak")
    for drawer_name in ("directives", "objectives", "build_ledger", "handoff"):
        assert drawer_name in registry["drawers"]


def test_handoff_dual_write_blocked_but_v2_allowed(tmp_path):
    api_main, api_key = _load_app(tmp_path)
    hat_id = "hat_contract_eval_handoff"
    headers = {"X-API-Key": api_key}

    with TestClient(api_main.app) as client:
        blocked = client.post(
            f"/v1/hats/{hat_id}/writeback/batch",
            headers=headers,
            json={
                "agent_id": "codex",
                "session_id": "sess_handoff",
                "events": [
                    {
                        "event_id": "evt_legacy_handoff",
                        "event_type": "handoff_legacy_write",
                        "payload": {"working_on": "legacy path"},
                        "timestamp": "2026-02-28T16:00:00Z",
                    }
                ],
            },
        )
        assert blocked.status_code == 200
        blocked_payload = blocked.json()
        assert blocked_payload["accepted"] == 0
        assert blocked_payload["rejected"] == 1
        assert blocked_payload["results"][0]["reason"] == "DUAL_HANDOFF_WRITE_BLOCKED"

        allowed = client.post(
            f"/v1/hats/{hat_id}/writeback/batch",
            headers=headers,
            json={
                "agent_id": "codex",
                "session_id": "sess_handoff",
                "events": [
                    {
                        "event_id": "evt_v2_handoff",
                        "event_type": "handoff_write",
                        "handoff_version": 2,
                        "payload": {"working_on": "canonical path"},
                        "timestamp": "2026-02-28T16:01:00Z",
                    }
                ],
            },
        )
        assert allowed.status_code == 200
        allowed_payload = allowed.json()
        assert allowed_payload["accepted"] == 1
        assert allowed_payload["rejected"] == 0
        assert allowed_payload["results"][0]["stored_as"] == "handoff_v2"

        status = client.get(f"/v1/hats/{hat_id}/status", headers=headers)
        assert status.status_code == 200
        status_payload = status.json()
        assert status_payload["handoff"]["handoff_version"] == 2


def test_phase2_intent_router_command_state_knowledge_audit(tmp_path):
    api_main, api_key = _load_app(tmp_path)
    hat_id = "hat_phase2_router"
    headers = {"X-API-Key": api_key}

    with TestClient(api_main.app) as client:
        remember = client.post(
            "/v1/remember",
            headers=headers,
            json={
                "content": "Knowledge: use exponential backoff for transient API failures.",
                "context": "engineering_knowledge",
                "importance": 0.7,
                "metadata": {"source": "phase2-test"},
            },
        )
        assert remember.status_code == 200

        writeback = client.post(
            f"/v1/hats/{hat_id}/writeback/batch",
            headers=headers,
            json={
                "agent_id": "codex",
                "session_id": "sess_phase2_router",
                "events": [
                    {
                        "event_id": "evt_phase2_dir",
                        "event_type": "directive_set",
                        "directive_id": "dir_phase2",
                        "content": "Do not let raw logs pollute command/state lanes.",
                        "priority": 99,
                        "timestamp": "2026-02-28T17:00:00Z",
                    },
                    {
                        "event_id": "evt_phase2_obj",
                        "event_type": "objective_upsert",
                        "objective_id": "obj-phase2",
                        "title": "Ship routing correctness",
                        "state": "done",
                        "timestamp": "2026-02-28T17:01:00Z",
                    },
                    {
                        "event_id": "evt_phase2_build",
                        "event_type": "build_logged",
                        "summary": "phase2 lane split completed",
                        "status": "ok",
                        "timestamp": "2026-02-28T17:02:00Z",
                    },
                    {
                        "event_id": "evt_phase2_raw",
                        "event_type": "observation",
                        "observation": "raw log audit breadcrumb",
                        "timestamp": "2026-02-28T17:03:00Z",
                    },
                ],
            },
        )
        assert writeback.status_code == 200
        assert writeback.json()["accepted"] == 4

        command_query = client.post(
            f"/v1/hats/{hat_id}/query",
            headers=headers,
            json={"query": "directive", "intent": "command", "limit": 10},
        )
        assert command_query.status_code == 200
        command_payload = command_query.json()
        assert command_payload["resolved_intent"] == "command"
        assert command_payload["results"]
        assert all(r["source_drawer"] == "directives" for r in command_payload["results"])

        state_query = client.post(
            f"/v1/hats/{hat_id}/query",
            headers=headers,
            json={"query": "objective status", "intent": "state", "limit": 10},
        )
        assert state_query.status_code == 200
        state_payload = state_query.json()
        assert state_payload["resolved_intent"] == "state"
        assert state_payload["results"]
        assert state_payload["results"][0]["record_type"] == "objective"
        assert state_payload["results"][0]["state"] == "done"
        assert all(r["source_drawer"] in {"objectives", "build_ledger"} for r in state_payload["results"])

        knowledge_query = client.post(
            f"/v1/hats/{hat_id}/query",
            headers=headers,
            json={"query": "exponential backoff", "intent": "knowledge", "limit": 10},
        )
        assert knowledge_query.status_code == 200
        knowledge_payload = knowledge_query.json()
        assert knowledge_payload["resolved_intent"] == "knowledge"
        assert knowledge_payload["results"]
        assert all(r["source_drawer"] == "knowledge" for r in knowledge_payload["results"])

        audit_query = client.post(
            f"/v1/hats/{hat_id}/query",
            headers=headers,
            json={"query": "raw log audit breadcrumb", "intent": "audit", "limit": 10},
        )
        assert audit_query.status_code == 200
        audit_payload = audit_query.json()
        assert audit_payload["resolved_intent"] == "audit"
        assert audit_payload["results"]
        assert all(r["source_drawer"] == "raw_log" for r in audit_payload["results"])


def test_phase3_lifecycle_and_filing_clerk(tmp_path):
    api_main, api_key = _load_app(tmp_path)
    hat_id = "hat_phase3_runtime"
    headers = {"X-API-Key": api_key}

    with TestClient(api_main.app) as client:
        # Upsert same objective twice; active record should remain single and latest.
        writeback_obj = client.post(
            f"/v1/hats/{hat_id}/writeback/batch",
            headers=headers,
            json={
                "agent_id": "codex",
                "session_id": "sess_phase3",
                "events": [
                    {
                        "event_id": "evt_obj_start",
                        "event_type": "objective_upsert",
                        "objective_id": "obj-phase3",
                        "title": "Ship filing clerk",
                        "state": "in_progress",
                        "timestamp": "2026-02-28T18:20:00Z",
                    },
                    {
                        "event_id": "evt_obj_done",
                        "event_type": "objective_upsert",
                        "objective_id": "obj-phase3",
                        "title": "Ship filing clerk",
                        "state": "done",
                        "timestamp": "2026-02-28T18:21:00Z",
                    },
                    {
                        "event_id": "evt_raw1",
                        "event_type": "observation",
                        "observation": "raw log one",
                        "timestamp": "2026-02-28T18:22:00Z",
                    },
                    {
                        "event_id": "evt_raw2",
                        "event_type": "observation",
                        "observation": "raw log two",
                        "timestamp": "2026-02-28T18:23:00Z",
                    },
                ],
            },
        )
        assert writeback_obj.status_code == 200

        # Maintenance should prune raw logs when retention is strict.
        maintenance = client.post(
            f"/v1/hats/{hat_id}/maintenance",
            headers=headers,
            json={"raw_log_keep": 1, "stale_objective_hours": 24},
        )
        assert maintenance.status_code == 200
        maint_payload = maintenance.json()
        assert maint_payload["raw_logs_pruned"] >= 1
        assert maint_payload["objective_archive_orphans"] == 0
        assert maint_payload["continuity_age_seconds"] >= 0.0

        status = client.get(f"/v1/hats/{hat_id}/status", headers=headers)
        assert status.status_code == 200
        status_payload = status.json()
        objectives = status_payload.get("objectives", [])
        obj_phase3 = [o for o in objectives if o.get("objective_id") == "obj-phase3"]
        assert len(obj_phase3) == 1
        assert obj_phase3[0]["state"] == "done"
        assert status_payload.get("meta", {}).get("continuity_age_seconds", 0.0) >= 0.0

        # Command/state lanes stay clean from raw_log contamination.
        command_query = client.post(
            f"/v1/hats/{hat_id}/query",
            headers=headers,
            json={"query": "directive", "intent": "command", "limit": 10},
        )
        assert command_query.status_code == 200
        assert all(r.get("source_drawer") == "directives" for r in command_query.json().get("results", []))

        state_query = client.post(
            f"/v1/hats/{hat_id}/query",
            headers=headers,
            json={"query": "objective status", "intent": "state", "limit": 10},
        )
        assert state_query.status_code == 200
        assert all(r.get("source_drawer") in {"objectives", "build_ledger"} for r in state_query.json().get("results", []))


def test_phase4_schema_and_runtime_contract(tmp_path):
    api_main, api_key = _load_app(tmp_path)
    hat_id = "hat_phase4_contract"
    headers = {"X-API-Key": api_key}

    with TestClient(api_main.app) as client:
        schema_before = client.get(f"/v1/hats/{hat_id}/schema", headers=headers)
        assert schema_before.status_code == 200
        schema_payload = schema_before.json()
        assert schema_payload["hat_instance_id"] == hat_id
        assert isinstance(schema_payload.get("hat_template", {}).get("drawers", []), list)
        assert len(schema_payload["hat_template"]["drawers"]) >= 1
        assert isinstance(schema_payload.get("runtime_state", {}), dict)

        writeback = client.post(
            f"/v1/hats/{hat_id}/writeback/batch",
            headers=headers,
            json={
                "agent_id": "codex",
                "session_id": "sess_phase4",
                "events": [
                    {
                        "event_id": "evt_phase4_dir",
                        "event_type": "directive_set",
                        "directive_id": "dir_phase4",
                        "content": "Track runtime state counters.",
                        "timestamp": "2026-02-28T20:10:00Z",
                    },
                    {
                        "event_id": "evt_phase4_raw",
                        "event_type": "observation",
                        "observation": "runtime-state breadcrumb",
                        "timestamp": "2026-02-28T20:10:01Z",
                    },
                ],
            },
        )
        assert writeback.status_code == 200
        assert writeback.json()["accepted"] == 2

        status = client.get(f"/v1/hats/{hat_id}/status", headers=headers)
        assert status.status_code == 200
        status_payload = status.json()
        runtime_state = status_payload.get("state", {}).get("runtime_state", {})
        assert runtime_state.get("total_writebacks", 0) >= 2
        assert runtime_state.get("total_batches", 0) >= 1
        assert "codex" in runtime_state.get("active_agents", [])
        drawer_stats = runtime_state.get("drawer_stats", [])
        assert any(
            row.get("drawer_id") == "directives" and int(row.get("confirmed_count", 0)) >= 1
            for row in drawer_stats
        )

        schema_after = client.get(f"/v1/hats/{hat_id}/schema", headers=headers)
        assert schema_after.status_code == 200
        schema_after_payload = schema_after.json()
        runtime_after = schema_after_payload.get("runtime_state", {})
        assert runtime_after.get("total_writebacks", 0) >= 2
