"""Unit tests for org-config 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
from src.services.auth import create_access_token

ORG_ID = uuid.uuid4()
USER_ID = uuid.uuid4()


def _auth_headers(role="owner"):
    token = create_access_token(
        user_id=USER_ID, organisation_id=ORG_ID, role=role, email="admin@test.com"
    )
    return {"Authorization": f"Bearer {token}"}


def _mock_org_config(**overrides):
    config = MagicMock()
    config.id = overrides.get("id", uuid.uuid4())
    config.organisation_id = overrides.get("organisation_id", ORG_ID)
    config.blocking_mode = overrides.get("blocking_mode")
    config.regional_modes = overrides.get("regional_modes")
    config.tcf_enabled = overrides.get("tcf_enabled")
    config.tcf_publisher_cc = overrides.get("tcf_publisher_cc")
    config.gcm_enabled = overrides.get("gcm_enabled")
    config.gcm_default = overrides.get("gcm_default")
    config.banner_config = overrides.get("banner_config")
    config.gpp_enabled = overrides.get("gpp_enabled")
    config.gpp_supported_apis = overrides.get("gpp_supported_apis")
    config.gpc_enabled = overrides.get("gpc_enabled")
    config.gpc_jurisdictions = overrides.get("gpc_jurisdictions")
    config.gpc_global_honour = overrides.get("gpc_global_honour")
    config.shopify_privacy_enabled = overrides.get("shopify_privacy_enabled")
    config.privacy_policy_url = overrides.get("privacy_policy_url")
    config.terms_url = overrides.get("terms_url")
    config.scan_schedule_cron = overrides.get("scan_schedule_cron")
    config.scan_max_pages = overrides.get("scan_max_pages")
    config.consent_expiry_days = overrides.get("consent_expiry_days")
    config.consent_retention_days = overrides.get("consent_retention_days")
    config.created_at = datetime.now(UTC)
    config.updated_at = datetime.now(UTC)
    return config


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


async def _client(app, mock_session):
    from src.db import get_db

    async def _override():
        yield mock_session

    app.dependency_overrides[get_db] = _override
    transport = ASGITransport(app=app)
    return AsyncClient(transport=transport, base_url="http://test")


def _mock_db_sequence(*results):
    """Create a mock session returning different results on successive execute() calls."""
    session = AsyncMock()
    mock_results = []
    for r in results:
        result = MagicMock()
        result.scalar_one_or_none.return_value = r
        mock_results.append(result)
    session.execute = AsyncMock(side_effect=mock_results)

    _added = []

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

    session.add = MagicMock(side_effect=_fake_add)

    async def _fake_flush():
        for obj in _added:
            if getattr(obj, "id", None) is None:
                obj.id = uuid.uuid4()
            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


class TestGetOrgConfig:
    @pytest.mark.asyncio
    async def test_get_existing_config(self, mock_app):
        """GET /org-config/ returns existing config."""
        config = _mock_org_config(blocking_mode="opt_out", consent_expiry_days=180)
        db = _mock_db_sequence(config)
        async with await _client(mock_app, db) as client:
            resp = await client.get("/api/v1/org-config/", headers=_auth_headers())
        assert resp.status_code == 200
        data = resp.json()
        assert data["blocking_mode"] == "opt_out"
        assert data["consent_expiry_days"] == 180

    @pytest.mark.asyncio
    async def test_get_auto_creates_when_missing(self, mock_app):
        """GET /org-config/ auto-creates a blank config if none exists."""
        db = _mock_db_sequence(None)  # no existing config
        async with await _client(mock_app, db) as client:
            resp = await client.get("/api/v1/org-config/", headers=_auth_headers())
        assert resp.status_code == 200
        data = resp.json()
        # All optional fields should be None
        assert data["blocking_mode"] is None
        assert data["tcf_enabled"] is None

    @pytest.mark.asyncio
    async def test_get_requires_auth(self, mock_app):
        """GET /org-config/ returns 401 without token."""
        db = _mock_db_sequence()
        async with await _client(mock_app, db) as client:
            resp = await client.get("/api/v1/org-config/")
        assert resp.status_code == 401


class TestUpdateOrgConfig:
    @pytest.mark.asyncio
    async def test_update_existing_config(self, mock_app):
        """PUT /org-config/ updates existing config."""
        config = _mock_org_config()
        db = _mock_db_sequence(config)
        async with await _client(mock_app, db) as client:
            resp = await client.put(
                "/api/v1/org-config/",
                json={"blocking_mode": "opt_out", "consent_expiry_days": 90},
                headers=_auth_headers(),
            )
        assert resp.status_code == 200
        # Verify setattr was called on the mock
        assert config.blocking_mode == "opt_out"
        assert config.consent_expiry_days == 90

    @pytest.mark.asyncio
    async def test_update_creates_when_missing(self, mock_app):
        """PUT /org-config/ creates config if none exists."""
        db = _mock_db_sequence(None)  # no existing config
        async with await _client(mock_app, db) as client:
            resp = await client.put(
                "/api/v1/org-config/",
                json={"tcf_enabled": True},
                headers=_auth_headers(),
            )
        assert resp.status_code == 200

    @pytest.mark.asyncio
    async def test_update_requires_admin(self, mock_app):
        """PUT /org-config/ returns 403 for viewers."""
        db = _mock_db_sequence()
        async with await _client(mock_app, db) as client:
            resp = await client.put(
                "/api/v1/org-config/",
                json={"blocking_mode": "opt_in"},
                headers=_auth_headers(role="viewer"),
            )
        assert resp.status_code == 403

    @pytest.mark.asyncio
    async def test_update_allows_editor_role_fails(self, mock_app):
        """PUT /org-config/ returns 403 for editors (only owner/admin can update)."""
        db = _mock_db_sequence()
        async with await _client(mock_app, db) as client:
            resp = await client.put(
                "/api/v1/org-config/",
                json={"blocking_mode": "opt_in"},
                headers=_auth_headers(role="editor"),
            )
        assert resp.status_code == 403
