"""
Integration tests for compliance audit session & sign-off routes.

Phase 1 Step 4 follow-up. The legacy ``compliance/tests/test_audit_routes.py``
contains placeholder tests that only assert on request-body shape — they do
not exercise the handler functions. This module uses a real FastAPI TestClient
against a sqlite-backed app so that handler logic, service delegation, domain
error translation, and response serialization are all covered end-to-end.

Covers:
  - POST/GET/PUT/DELETE /audit/sessions (and lifecycle transitions)
  - GET /audit/checklist/{session_id} (pagination + filters)
  - PUT /audit/checklist/{session_id}/items/{requirement_id}/sign-off
  - GET /audit/checklist/{session_id}/items/{requirement_id}
  - Error cases: 404 (not found), 409 (invalid state transition), 400 (bad input)
"""

import os
import sys
import uuid

import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import StaticPool

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

from classroom_engine.database import Base, get_db  # noqa: E402
from compliance.api.audit_routes import router as audit_router  # noqa: E402
from compliance.db.models import (  # noqa: E402
    ControlDB,
    ControlDomainEnum,
    ControlStatusEnum,
    ControlTypeEnum,
    RegulationDB,
    RegulationTypeEnum,
    RequirementDB,
)

engine = create_engine(
    "sqlite://",
    connect_args={"check_same_thread": False},
    poolclass=StaticPool,
)
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

app = FastAPI()
app.include_router(audit_router, prefix="/api/compliance")


def override_get_db():
    db = TestingSessionLocal()
    try:
        yield db
    finally:
        db.close()


app.dependency_overrides[get_db] = override_get_db
client = TestClient(app)


@pytest.fixture(scope="module", autouse=True)
def _schema():
    Base.metadata.create_all(bind=engine)
    yield
    Base.metadata.drop_all(bind=engine)


@pytest.fixture(autouse=True)
def _wipe_data():
    """Wipe all rows between tests without recreating the schema."""
    yield
    with engine.begin() as conn:
        for table in reversed(Base.metadata.sorted_tables):
            conn.execute(table.delete())


# ============================================================================
# Fixtures
# ============================================================================


@pytest.fixture
def seeded_requirements():
    """Seed a regulation + 3 requirements so audit sessions have scope."""
    db = TestingSessionLocal()
    try:
        reg = RegulationDB(
            id=str(uuid.uuid4()),
            code="GDPR",
            name="GDPR",
            regulation_type=RegulationTypeEnum.EU_REGULATION,
        )
        db.add(reg)
        db.flush()
        req_ids = []
        for i in range(3):
            req = RequirementDB(
                id=str(uuid.uuid4()),
                regulation_id=reg.id,
                article=f"Art. {i + 1}",
                title=f"Requirement {i + 1}",
                description=f"Desc {i + 1}",
                implementation_status="not_started",
                priority=2,
            )
            db.add(req)
            req_ids.append(req.id)
        db.commit()
        yield {"regulation_id": reg.id, "requirement_ids": req_ids}
    finally:
        db.close()


def _create_session(name="Test Audit", codes=None):
    r = client.post(
        "/api/compliance/audit/sessions",
        json={
            "name": name,
            "description": "Integration test",
            "auditor_name": "Dr. Test",
            "auditor_email": "test@example.com",
            "regulation_codes": codes,
        },
    )
    assert r.status_code == 200, r.text
    return r.json()


# ============================================================================
# Session lifecycle
# ============================================================================


class TestSessionCreate:
    def test_create_session_without_scope_ok(self):
        r = client.post(
            "/api/compliance/audit/sessions",
            json={"name": "No scope", "auditor_name": "Someone"},
        )
        assert r.status_code == 200
        body = r.json()
        assert body["name"] == "No scope"
        assert body["status"] == "draft"
        assert body["total_items"] == 0
        assert body["completion_percentage"] == 0.0

    def test_create_session_with_regulation_filter_counts_requirements(
        self, seeded_requirements
    ):
        body = _create_session(codes=["GDPR"])
        assert body["total_items"] == 3
        assert body["regulation_ids"] == ["GDPR"]


class TestSessionList:
    def test_list_empty(self):
        r = client.get("/api/compliance/audit/sessions")
        assert r.status_code == 200
        assert r.json() == []

    def test_list_filters_by_status(self):
        a = _create_session("A")
        _create_session("B")
        # Start one -> in_progress
        client.put(f"/api/compliance/audit/sessions/{a['id']}/start")
        r = client.get("/api/compliance/audit/sessions?status=draft")
        assert r.status_code == 200
        assert len(r.json()) == 1
        assert r.json()[0]["name"] == "B"

    def test_list_invalid_status_returns_400(self):
        r = client.get("/api/compliance/audit/sessions?status=bogus")
        assert r.status_code == 400
        assert "Invalid status" in r.json()["detail"]


class TestSessionGet:
    def test_get_not_found_returns_404(self):
        r = client.get("/api/compliance/audit/sessions/missing")
        assert r.status_code == 404

    def test_get_existing_returns_details_with_stats(self, seeded_requirements):
        s = _create_session(codes=["GDPR"])
        r = client.get(f"/api/compliance/audit/sessions/{s['id']}")
        assert r.status_code == 200
        body = r.json()
        assert body["id"] == s["id"]
        assert body["statistics"]["total"] == 3
        assert body["statistics"]["pending"] == 3


class TestSessionTransitions:
    def test_start_from_draft_ok(self):
        s = _create_session()
        r = client.put(f"/api/compliance/audit/sessions/{s['id']}/start")
        assert r.status_code == 200
        assert r.json()["status"] == "in_progress"

    def test_start_from_completed_returns_409(self):
        s = _create_session()
        client.put(f"/api/compliance/audit/sessions/{s['id']}/start")
        client.put(f"/api/compliance/audit/sessions/{s['id']}/complete")
        r = client.put(f"/api/compliance/audit/sessions/{s['id']}/start")
        assert r.status_code == 409

    def test_complete_from_draft_returns_409(self):
        s = _create_session()
        r = client.put(f"/api/compliance/audit/sessions/{s['id']}/complete")
        assert r.status_code == 409

    def test_full_lifecycle_draft_inprogress_completed_archived(self):
        s = _create_session()
        assert client.put(f"/api/compliance/audit/sessions/{s['id']}/start").status_code == 200
        assert client.put(f"/api/compliance/audit/sessions/{s['id']}/complete").status_code == 200
        assert client.put(f"/api/compliance/audit/sessions/{s['id']}/archive").status_code == 200
        r = client.get(f"/api/compliance/audit/sessions/{s['id']}")
        assert r.json()["status"] == "archived"

    def test_archive_from_inprogress_returns_409(self):
        s = _create_session()
        client.put(f"/api/compliance/audit/sessions/{s['id']}/start")
        r = client.put(f"/api/compliance/audit/sessions/{s['id']}/archive")
        assert r.status_code == 409


class TestSessionDelete:
    def test_delete_draft_ok(self):
        s = _create_session()
        r = client.delete(f"/api/compliance/audit/sessions/{s['id']}")
        assert r.status_code == 200
        assert client.get(f"/api/compliance/audit/sessions/{s['id']}").status_code == 404

    def test_delete_in_progress_returns_409(self):
        s = _create_session()
        client.put(f"/api/compliance/audit/sessions/{s['id']}/start")
        r = client.delete(f"/api/compliance/audit/sessions/{s['id']}")
        assert r.status_code == 409

    def test_delete_missing_returns_404(self):
        r = client.delete("/api/compliance/audit/sessions/missing")
        assert r.status_code == 404


# ============================================================================
# Checklist & sign-off
# ============================================================================


class TestChecklist:
    def test_checklist_returns_paginated_items(self, seeded_requirements):
        s = _create_session(codes=["GDPR"])
        r = client.get(f"/api/compliance/audit/checklist/{s['id']}?page=1&page_size=2")
        assert r.status_code == 200
        body = r.json()
        assert len(body["items"]) == 2
        assert body["pagination"]["total"] == 3
        assert body["pagination"]["has_next"] is True
        assert body["pagination"]["has_prev"] is False
        assert body["statistics"]["pending"] == 3

    def test_checklist_session_not_found_returns_404(self):
        r = client.get("/api/compliance/audit/checklist/nope")
        assert r.status_code == 404

    def test_checklist_search_filters_by_title(self, seeded_requirements):
        s = _create_session(codes=["GDPR"])
        r = client.get(
            f"/api/compliance/audit/checklist/{s['id']}?search=Requirement 2"
        )
        assert r.status_code == 200
        titles = [i["title"] for i in r.json()["items"]]
        assert titles == ["Requirement 2"]


class TestSignOff:
    def test_sign_off_creates_record_and_auto_starts_session(
        self, seeded_requirements
    ):
        s = _create_session(codes=["GDPR"])
        req_id = seeded_requirements["requirement_ids"][0]
        r = client.put(
            f"/api/compliance/audit/checklist/{s['id']}/items/{req_id}/sign-off",
            json={"result": "compliant", "notes": "all good", "sign": False},
        )
        assert r.status_code == 200
        assert r.json()["result"] == "compliant"
        # Session auto-starts on first sign-off
        got = client.get(f"/api/compliance/audit/sessions/{s['id']}").json()
        assert got["status"] == "in_progress"
        assert got["statistics"]["compliant"] == 1
        assert got["statistics"]["pending"] == 2

    def test_sign_off_with_signature_creates_hash(self, seeded_requirements):
        s = _create_session(codes=["GDPR"])
        req_id = seeded_requirements["requirement_ids"][0]
        r = client.put(
            f"/api/compliance/audit/checklist/{s['id']}/items/{req_id}/sign-off",
            json={"result": "compliant", "sign": True},
        )
        body = r.json()
        assert body["is_signed"] is True
        assert body["signature_hash"] and len(body["signature_hash"]) == 64
        assert body["signed_by"] == "Dr. Test"

    def test_sign_off_update_existing_record_flips_counts(self, seeded_requirements):
        s = _create_session(codes=["GDPR"])
        req_id = seeded_requirements["requirement_ids"][0]
        client.put(
            f"/api/compliance/audit/checklist/{s['id']}/items/{req_id}/sign-off",
            json={"result": "compliant"},
        )
        client.put(
            f"/api/compliance/audit/checklist/{s['id']}/items/{req_id}/sign-off",
            json={"result": "non_compliant"},
        )
        stats = client.get(f"/api/compliance/audit/sessions/{s['id']}").json()["statistics"]
        assert stats["compliant"] == 0
        assert stats["non_compliant"] == 1

    def test_sign_off_invalid_result_returns_400(self, seeded_requirements):
        s = _create_session(codes=["GDPR"])
        req_id = seeded_requirements["requirement_ids"][0]
        r = client.put(
            f"/api/compliance/audit/checklist/{s['id']}/items/{req_id}/sign-off",
            json={"result": "bogus"},
        )
        assert r.status_code == 400
        assert "Invalid result" in r.json()["detail"]

    def test_sign_off_missing_requirement_returns_404(self, seeded_requirements):
        s = _create_session(codes=["GDPR"])
        r = client.put(
            f"/api/compliance/audit/checklist/{s['id']}/items/nope/sign-off",
            json={"result": "compliant"},
        )
        assert r.status_code == 404

    def test_sign_off_on_completed_session_returns_409(self, seeded_requirements):
        s = _create_session(codes=["GDPR"])
        req_id = seeded_requirements["requirement_ids"][0]
        client.put(f"/api/compliance/audit/sessions/{s['id']}/start")
        client.put(f"/api/compliance/audit/sessions/{s['id']}/complete")
        r = client.put(
            f"/api/compliance/audit/checklist/{s['id']}/items/{req_id}/sign-off",
            json={"result": "compliant"},
        )
        assert r.status_code == 409

    def test_get_sign_off_returns_404_when_missing(self, seeded_requirements):
        s = _create_session(codes=["GDPR"])
        req_id = seeded_requirements["requirement_ids"][0]
        r = client.get(
            f"/api/compliance/audit/checklist/{s['id']}/items/{req_id}"
        )
        assert r.status_code == 404

    def test_get_sign_off_returns_existing(self, seeded_requirements):
        s = _create_session(codes=["GDPR"])
        req_id = seeded_requirements["requirement_ids"][0]
        client.put(
            f"/api/compliance/audit/checklist/{s['id']}/items/{req_id}/sign-off",
            json={"result": "compliant", "notes": "ok"},
        )
        r = client.get(
            f"/api/compliance/audit/checklist/{s['id']}/items/{req_id}"
        )
        assert r.status_code == 200
        assert r.json()["result"] == "compliant"
        assert r.json()["notes"] == "ok"
