"""Unit tests for consent router — mocked database."""

import uuid
from datetime import UTC, datetime
from unittest.mock import AsyncMock, MagicMock

import pytest
from httpx import ASGITransport, AsyncClient

from src.main import create_app


def _mock_consent_record(**overrides):
    """Build a mock ConsentRecord ORM object."""
    record = MagicMock()
    record.id = overrides.get("id", uuid.uuid4())
    record.site_id = overrides.get("site_id", uuid.uuid4())
    record.visitor_id = overrides.get("visitor_id", "visitor-123")
    record.ip_hash = "abc123"
    record.user_agent_hash = "def456"
    record.action = overrides.get("action", "accept_all")
    record.categories_accepted = overrides.get("categories_accepted", ["necessary"])
    record.categories_rejected = overrides.get("categories_rejected", [])
    record.tc_string = overrides.get("tc_string")
    record.gcm_state = overrides.get("gcm_state")
    record.gpp_string = overrides.get("gpp_string")
    record.gpc_detected = overrides.get("gpc_detected")
    record.gpc_honoured = overrides.get("gpc_honoured")
    record.page_url = overrides.get("page_url")
    record.country_code = overrides.get("country_code")
    record.region_code = overrides.get("region_code")
    record.consented_at = overrides.get("consented_at", datetime.now(UTC))
    return record


def _mock_db(scalar_one_or_none=None):
    session = AsyncMock()
    result = MagicMock()
    result.scalar_one_or_none.return_value = scalar_one_or_none
    session.execute.return_value = result

    _added_objects = []

    def _fake_add(obj):
        _added_objects.append(obj)

    session.add = MagicMock(side_effect=_fake_add)

    async def _fake_flush():
        """Simulate DB flush — populate server-side defaults."""
        for obj in _added_objects:
            if getattr(obj, "id", None) is None:
                obj.id = uuid.uuid4()
            if hasattr(obj, "consented_at") and getattr(obj, "consented_at", None) is None:
                obj.consented_at = datetime.now(UTC)
            if hasattr(obj, "created_at") and getattr(obj, "created_at", None) is None:
                obj.created_at = datetime.now(UTC)
            if hasattr(obj, "updated_at") and getattr(obj, "updated_at", None) is None:
                obj.updated_at = datetime.now(UTC)

    session.flush = AsyncMock(side_effect=_fake_flush)
    session.refresh = AsyncMock()
    return session


@pytest.fixture
def mock_app():
    return create_app()


async def _client(app, mock_session):
    from src.db import get_db
    from src.services.dependencies import get_current_user, require_role

    user = MagicMock()
    user.organisation_id = uuid.uuid4()
    user.role = "owner"

    async def _override():
        yield mock_session

    app.dependency_overrides[get_db] = _override
    app.dependency_overrides[get_current_user] = lambda: user

    def _override_require_role(*_roles):
        return lambda: user

    app.dependency_overrides[require_role] = _override_require_role

    transport = ASGITransport(app=app)
    return AsyncClient(transport=transport, base_url="http://test")


class TestRecordConsent:
    @pytest.mark.asyncio
    async def test_record_consent_success(self, mock_app):
        db = _mock_db()
        async with await _client(mock_app, db) as client:
            resp = await client.post(
                "/api/v1/consent/",
                json={
                    "site_id": str(uuid.uuid4()),
                    "visitor_id": "visitor-123",
                    "action": "accept_all",
                    "categories_accepted": ["necessary", "analytics"],
                    "categories_rejected": [],
                },
            )
        assert resp.status_code == 201

    @pytest.mark.asyncio
    async def test_record_consent_reject_all(self, mock_app):
        db = _mock_db()
        async with await _client(mock_app, db) as client:
            resp = await client.post(
                "/api/v1/consent/",
                json={
                    "site_id": str(uuid.uuid4()),
                    "visitor_id": "visitor-456",
                    "action": "reject_all",
                    "categories_accepted": ["necessary"],
                    "categories_rejected": ["analytics", "marketing"],
                },
            )
        assert resp.status_code == 201

    @pytest.mark.asyncio
    async def test_record_consent_custom(self, mock_app):
        db = _mock_db()
        async with await _client(mock_app, db) as client:
            resp = await client.post(
                "/api/v1/consent/",
                json={
                    "site_id": str(uuid.uuid4()),
                    "visitor_id": "visitor-789",
                    "action": "custom",
                    "categories_accepted": ["necessary", "analytics"],
                    "categories_rejected": ["marketing"],
                },
            )
        assert resp.status_code == 201

    @pytest.mark.asyncio
    async def test_record_consent_invalid_action(self, mock_app):
        db = _mock_db()
        async with await _client(mock_app, db) as client:
            resp = await client.post(
                "/api/v1/consent/",
                json={
                    "site_id": str(uuid.uuid4()),
                    "visitor_id": "visitor-000",
                    "action": "invalid_action",
                    "categories_accepted": ["necessary"],
                    "categories_rejected": [],
                },
            )
        assert resp.status_code == 422

    @pytest.mark.asyncio
    async def test_record_consent_empty_visitor_id(self, mock_app):
        db = _mock_db()
        async with await _client(mock_app, db) as client:
            resp = await client.post(
                "/api/v1/consent/",
                json={
                    "site_id": str(uuid.uuid4()),
                    "visitor_id": "",
                    "action": "accept_all",
                    "categories_accepted": ["necessary"],
                    "categories_rejected": [],
                },
            )
        assert resp.status_code == 422

    @pytest.mark.asyncio
    async def test_record_consent_with_optional_fields(self, mock_app):
        db = _mock_db()
        async with await _client(mock_app, db) as client:
            resp = await client.post(
                "/api/v1/consent/",
                json={
                    "site_id": str(uuid.uuid4()),
                    "visitor_id": "visitor-opt",
                    "action": "accept_all",
                    "categories_accepted": ["necessary"],
                    "categories_rejected": [],
                    "tc_string": "CPXxRAAAA",
                    "gcm_state": {"analytics_storage": "granted"},
                    "page_url": "https://example.com",
                    "country_code": "GB",
                    "region_code": "ENG",
                },
            )
        assert resp.status_code == 201


class TestGetConsent:
    @pytest.mark.asyncio
    async def test_get_consent_found(self, mock_app):
        record = _mock_consent_record()
        db = _mock_db(scalar_one_or_none=record)
        async with await _client(mock_app, db) as client:
            resp = await client.get(f"/api/v1/consent/{record.id}")
        assert resp.status_code == 200

    @pytest.mark.asyncio
    async def test_get_consent_not_found(self, mock_app):
        db = _mock_db(scalar_one_or_none=None)
        async with await _client(mock_app, db) as client:
            resp = await client.get(f"/api/v1/consent/{uuid.uuid4()}")
        assert resp.status_code == 404


class TestVerifyConsent:
    @pytest.mark.asyncio
    async def test_verify_consent_valid(self, mock_app):
        record = _mock_consent_record()
        db = _mock_db(scalar_one_or_none=record)
        async with await _client(mock_app, db) as client:
            resp = await client.get(f"/api/v1/consent/verify/{record.id}")
        assert resp.status_code == 200
        data = resp.json()
        assert data["valid"] is True

    @pytest.mark.asyncio
    async def test_verify_consent_not_found(self, mock_app):
        db = _mock_db(scalar_one_or_none=None)
        async with await _client(mock_app, db) as client:
            resp = await client.get(f"/api/v1/consent/verify/{uuid.uuid4()}")
        assert resp.status_code == 404
