"""
Integration tests for the audit logging system.

Tests:
  - AuditLogger writes correct rows to audit_events
  - audit_events is immutable (UPDATE/DELETE rejected by DB)
  - RLS isolates audit events per tenant
  - KBChunk and KBDocument models exist and map to migration tables
"""

from __future__ import annotations

import uuid

import pytest
import pytest_asyncio
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession

# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest_asyncio.fixture
async def audit_tenant(db_session: AsyncSession) -> dict:
    """Create a tenant for audit tests."""
    tenant_id = uuid.uuid4()
    suffix = uuid.uuid4().hex[:6]
    await db_session.execute(
        text("INSERT INTO tenants (id, name, slug, settings) VALUES (:id, :name, :slug, :settings)"),
        {
            "id": str(tenant_id),
            "name": f"Audit Tenant {suffix}",
            "slug": f"audit-tenant-{suffix}",
            "settings": "{}",
        },
    )
    await db_session.commit()
    return {"id": tenant_id}


@pytest_asyncio.fixture
async def audit_logger_fixture(db_engine):
    """Return an AuditLogger wired to the test engine."""
    from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker

    from orchestrator.audit.logger import AuditLogger

    session_factory = async_sessionmaker(db_engine, class_=AsyncSession, expire_on_commit=False)
    return AuditLogger(session_factory=session_factory)


# ---------------------------------------------------------------------------
# AuditLogger tests
# ---------------------------------------------------------------------------


class TestAuditLoggerLlmCall:
    """AuditLogger.log_llm_call writes correct audit_events rows."""

    async def test_llm_call_written_to_db(self, audit_logger_fixture, db_session, audit_tenant, db_engine):
        from shared.rls import configure_rls_hook, current_tenant_id

        tenant_id = audit_tenant["id"]
        configure_rls_hook(db_engine)
        token = current_tenant_id.set(tenant_id)
        try:
            await audit_logger_fixture.log_llm_call(
                tenant_id=tenant_id,
                agent_id=uuid.uuid4(),
                user_id="test-user",
                input_summary="What is the weather?",
                output_summary="It is sunny.",
                latency_ms=350,
                metadata={"model": "quality"},
            )
        finally:
            current_tenant_id.reset(token)

        # Verify row was written
        token2 = current_tenant_id.set(tenant_id)
        try:
            result = await db_session.execute(
                text(
                    "SELECT action_type, input_summary, output_summary, latency_ms "
                    "FROM audit_events WHERE tenant_id = :tid ORDER BY created_at DESC LIMIT 1"
                ),
                {"tid": str(tenant_id)},
            )
        finally:
            current_tenant_id.reset(token2)

        row = result.fetchone()
        assert row is not None
        assert row.action_type == "llm_call"
        assert row.input_summary == "What is the weather?"
        assert row.output_summary == "It is sunny."
        assert row.latency_ms == 350

    async def test_tool_call_written_to_db(self, audit_logger_fixture, db_session, audit_tenant, db_engine):
        from shared.rls import configure_rls_hook, current_tenant_id

        tenant_id = audit_tenant["id"]
        configure_rls_hook(db_engine)
        token = current_tenant_id.set(tenant_id)
        try:
            await audit_logger_fixture.log_tool_call(
                tool_name="web_search",
                args={"query": "latest AI news"},
                result="Top AI news: ...",
                tenant_id=tenant_id,
                agent_id=uuid.uuid4(),
                latency_ms=1200,
            )
        finally:
            current_tenant_id.reset(token)

        token2 = current_tenant_id.set(tenant_id)
        try:
            result = await db_session.execute(
                text(
                    "SELECT action_type, input_summary, latency_ms "
                    "FROM audit_events WHERE tenant_id = :tid ORDER BY created_at DESC LIMIT 1"
                ),
                {"tid": str(tenant_id)},
            )
        finally:
            current_tenant_id.reset(token2)

        row = result.fetchone()
        assert row is not None
        assert row.action_type == "tool_invocation"
        assert "web_search" in row.input_summary
        assert row.latency_ms == 1200

    async def test_escalation_written_to_db(self, audit_logger_fixture, db_session, audit_tenant, db_engine):
        from shared.rls import configure_rls_hook, current_tenant_id

        tenant_id = audit_tenant["id"]
        configure_rls_hook(db_engine)
        token = current_tenant_id.set(tenant_id)
        try:
            await audit_logger_fixture.log_escalation(
                tenant_id=tenant_id,
                agent_id=uuid.uuid4(),
                user_id="test-user",
                trigger_reason="sentiment < -0.7",
                metadata={"sentiment_score": -0.85},
            )
        finally:
            current_tenant_id.reset(token)

        token2 = current_tenant_id.set(tenant_id)
        try:
            result = await db_session.execute(
                text(
                    "SELECT action_type, input_summary "
                    "FROM audit_events WHERE tenant_id = :tid ORDER BY created_at DESC LIMIT 1"
                ),
                {"tid": str(tenant_id)},
            )
        finally:
            current_tenant_id.reset(token2)

        row = result.fetchone()
        assert row is not None
        assert row.action_type == "escalation"
        assert "sentiment < -0.7" in row.input_summary


class TestAuditImmutability:
    """audit_events rows cannot be updated or deleted by konstruct_app."""

    async def test_update_rejected(self, audit_logger_fixture, db_session, audit_tenant, db_engine):
        """UPDATE on audit_events must raise a permission error."""
        from sqlalchemy.exc import ProgrammingError

        from shared.rls import configure_rls_hook, current_tenant_id

        tenant_id = audit_tenant["id"]
        configure_rls_hook(db_engine)
        token = current_tenant_id.set(tenant_id)
        try:
            await audit_logger_fixture.log_llm_call(
                tenant_id=tenant_id,
                agent_id=uuid.uuid4(),
                user_id="test-user",
                input_summary="original",
                output_summary="original output",
                latency_ms=100,
            )
        finally:
            current_tenant_id.reset(token)

        token3 = current_tenant_id.set(tenant_id)
        try:
            with pytest.raises(ProgrammingError):
                await db_session.execute(
                    text("UPDATE audit_events SET input_summary = 'tampered' WHERE tenant_id = :tid"),
                    {"tid": str(tenant_id)},
                )
                await db_session.commit()
        finally:
            current_tenant_id.reset(token3)
            await db_session.rollback()

    async def test_delete_rejected(self, audit_logger_fixture, db_session, audit_tenant, db_engine):
        """DELETE on audit_events must raise a permission error."""
        from sqlalchemy.exc import ProgrammingError

        from shared.rls import configure_rls_hook, current_tenant_id

        tenant_id = audit_tenant["id"]
        configure_rls_hook(db_engine)
        token = current_tenant_id.set(tenant_id)
        try:
            await audit_logger_fixture.log_llm_call(
                tenant_id=tenant_id,
                agent_id=uuid.uuid4(),
                user_id="test-user",
                input_summary="to be deleted",
                output_summary="output",
                latency_ms=50,
            )
        finally:
            current_tenant_id.reset(token)

        token3 = current_tenant_id.set(tenant_id)
        try:
            with pytest.raises(ProgrammingError):
                await db_session.execute(
                    text("DELETE FROM audit_events WHERE tenant_id = :tid"),
                    {"tid": str(tenant_id)},
                )
                await db_session.commit()
        finally:
            current_tenant_id.reset(token3)
            await db_session.rollback()


class TestAuditRLS:
    """Tenant A cannot see Tenant B's audit events via RLS."""

    async def test_rls_isolation(self, db_session, db_engine, tenant_a, tenant_b):
        """Tenant A cannot read Tenant B's audit rows."""
        from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker

        from orchestrator.audit.logger import AuditLogger
        from shared.rls import configure_rls_hook, current_tenant_id

        configure_rls_hook(db_engine)
        session_factory = async_sessionmaker(db_engine, class_=AsyncSession, expire_on_commit=False)
        audit_logger = AuditLogger(session_factory=session_factory)

        # Write an event for tenant_b
        token_b = current_tenant_id.set(tenant_b["id"])
        try:
            await audit_logger.log_llm_call(
                tenant_id=tenant_b["id"],
                agent_id=uuid.uuid4(),
                user_id="user-b",
                input_summary="tenant b message",
                output_summary="tenant b response",
                latency_ms=200,
            )
        finally:
            current_tenant_id.reset(token_b)

        # Query from tenant_a's context — should return no rows
        token_a = current_tenant_id.set(tenant_a["id"])
        try:
            result = await db_session.execute(
                text("SELECT COUNT(*) FROM audit_events WHERE tenant_id = :tid"),
                {"tid": str(tenant_b["id"])},
            )
            count = result.scalar()
        finally:
            current_tenant_id.reset(token_a)

        assert count == 0, "Tenant A should not see Tenant B's audit events"
