"""Backend test coverage gaps — Phase 7 Task 7.8.

Tests for routes that lack dedicated test coverage:
- Currencies CRUD + edge cases (invalid UUID, not found, validation)
- Sequences CRUD + edge cases
- System settings get/upsert
- Contact folders CRUD + edge cases
- Notifications edge cases (invalid UUID, not found)
- Entity history (not found, invalid entity)
- Multi-tenant isolation (cross-tenant access returns 404/403)

These tests run in CI/CD with PostgreSQL + Redis. They cannot run
in this container (no PostgreSQL/Redis available).
"""

from __future__ import annotations

import pytest
from httpx import AsyncClient

from tests.conftest import ORIGIN_HEADER, login_client, seed_tenant_and_users


# ─── Currencies Tests ───


class TestCurrenciesRoutes:
    """Tests for /api/v1/currencies — CRUD and edge cases."""

    @pytest.mark.asyncio
    async def test_list_currencies_empty_returns_200(self, client: AsyncClient, db_session):
        """GET /currencies returns 200 with empty list when no currencies exist."""
        await seed_tenant_and_users(db_session)
        await login_client(client, "admin@tenanta.com")
        resp = await client.get("/api/v1/currencies", headers=ORIGIN_HEADER)
        assert resp.status_code == 200
        data = resp.json()
        assert data["items"] == []
        assert data["total"] == 0

    @pytest.mark.asyncio
    async def test_create_currency_returns_201(self, client: AsyncClient, db_session):
        """POST /currencies creates a currency and returns 201."""
        await seed_tenant_and_users(db_session)
        await login_client(client, "admin@tenanta.com")
        resp = await client.post(
            "/api/v1/currencies",
            json={"code": "EUR", "name": "Euro", "symbol": "€", "is_default": True},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 201
        data = resp.json()
        assert data["code"] == "EUR"
        assert data["name"] == "Euro"
        assert data["symbol"] == "€"
        assert data["is_default"] is True

    @pytest.mark.asyncio
    async def test_create_currency_invalid_code_length_returns_422(self, client: AsyncClient, db_session):
        """POST /currencies with invalid code length returns 422."""
        await seed_tenant_and_users(db_session)
        await login_client(client, "admin@tenanta.com")
        resp = await client.post(
            "/api/v1/currencies",
            json={"code": "EURO", "name": "Euro", "symbol": "€"},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 422

    @pytest.mark.asyncio
    async def test_update_currency_invalid_uuid_returns_400(self, client: AsyncClient, db_session):
        """PATCH /currencies/{invalid_uuid} returns 400."""
        await seed_tenant_and_users(db_session)
        await login_client(client, "admin@tenanta.com")
        resp = await client.patch(
            "/api/v1/currencies/not-a-uuid",
            json={"name": "Updated"},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code in (400, 404)

    @pytest.mark.asyncio
    async def test_delete_currency_not_found_returns_404(self, client: AsyncClient, db_session):
        """DELETE /currencies/{nonexistent_uuid} returns 404."""
        await seed_tenant_and_users(db_session)
        await login_client(client, "admin@tenanta.com")
        resp = await client.delete(
            "/api/v1/currencies/00000000-0000-0000-0000-000000000000",
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 404

    @pytest.mark.asyncio
    async def test_viewer_cannot_create_currency_returns_403(self, client: AsyncClient, db_session):
        """Viewer role cannot create currencies (requires currencies:write)."""
        await seed_tenant_and_users(db_session)
        await login_client(client, "viewer@tenanta.com")
        resp = await client.post(
            "/api/v1/currencies",
            json={"code": "USD", "name": "US Dollar", "symbol": "$"},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 403


# ─── Sequences Tests ───


class TestSequencesRoutes:
    """Tests for /api/v1/sequences — CRUD and edge cases."""

    @pytest.mark.asyncio
    async def test_list_sequences_empty_returns_200(self, client: AsyncClient, db_session):
        """GET /sequences returns 200 with empty list."""
        await seed_tenant_and_users(db_session)
        await login_client(client, "admin@tenanta.com")
        resp = await client.get("/api/v1/sequences", headers=ORIGIN_HEADER)
        assert resp.status_code == 200
        data = resp.json()
        assert data["items"] == []
        assert data["total"] == 0

    @pytest.mark.asyncio
    async def test_create_sequence_returns_201(self, client: AsyncClient, db_session):
        """POST /sequences creates a sequence and returns 201."""
        await seed_tenant_and_users(db_session)
        await login_client(client, "admin@tenanta.com")
        resp = await client.post(
            "/api/v1/sequences",
            json={"name": "invoice", "prefix": "RE-2026-", "padding": 4},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 201
        data = resp.json()
        assert data["name"] == "invoice"
        assert data["prefix"] == "RE-2026-"
        assert data["padding"] == 4
        assert data["next_number"] == 1

    @pytest.mark.asyncio
    async def test_create_sequence_invalid_padding_returns_422(self, client: AsyncClient, db_session):
        """POST /sequences with padding > 10 returns 422."""
        await seed_tenant_and_users(db_session)
        await login_client(client, "admin@tenanta.com")
        resp = await client.post(
            "/api/v1/sequences",
            json={"name": "test", "prefix": "", "padding": 15},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 422

    @pytest.mark.asyncio
    async def test_delete_sequence_not_found_returns_404(self, client: AsyncClient, db_session):
        """DELETE /sequences/{nonexistent_uuid} returns 404."""
        await seed_tenant_and_users(db_session)
        await login_client(client, "admin@tenanta.com")
        resp = await client.delete(
            "/api/v1/sequences/00000000-0000-0000-0000-000000000000",
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 404


# ─── System Settings Tests ───


class TestSystemSettingsRoutes:
    """Tests for /api/v1/system-settings — get and upsert."""

    @pytest.mark.asyncio
    async def test_get_system_settings_returns_200(self, client: AsyncClient, db_session):
        """GET /system-settings returns 200 with default settings."""
        await seed_tenant_and_users(db_session)
        await login_client(client, "admin@tenanta.com")
        resp = await client.get("/api/v1/system-settings", headers=ORIGIN_HEADER)
        assert resp.status_code == 200
        data = resp.json()
        assert "company_name" in data

    @pytest.mark.asyncio
    async def test_upsert_system_settings_returns_200(self, client: AsyncClient, db_session):
        """PUT /system-settings upserts settings and returns 200."""
        await seed_tenant_and_users(db_session)
        await login_client(client, "admin@tenanta.com")
        resp = await client.put(
            "/api/v1/system-settings",
            json={
                "company_name": "Test GmbH",
                "company_street": "Teststr. 1",
                "company_city": "Berlin",
                "company_zip": "10115",
                "company_country": "DE",
            },
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 200
        data = resp.json()
        assert data["company_name"] == "Test GmbH"
        assert data["company_city"] == "Berlin"

    @pytest.mark.asyncio
    async def test_upsert_system_settings_missing_required_field_returns_422(
        self, client: AsyncClient, db_session
    ):
        """PUT /system-settings with missing required field returns 422."""
        await seed_tenant_and_users(db_session)
        await login_client(client, "admin@tenanta.com")
        resp = await client.put(
            "/api/v1/system-settings",
            json={"company_name": "Test GmbH"},  # missing required fields
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 422

    @pytest.mark.asyncio
    async def test_viewer_cannot_upsert_system_settings_returns_403(
        self, client: AsyncClient, db_session
    ):
        """Viewer role cannot upsert system settings (requires settings:write)."""
        await seed_tenant_and_users(db_session)
        await login_client(client, "viewer@tenanta.com")
        resp = await client.put(
            "/api/v1/system-settings",
            json={
                "company_name": "Test",
                "company_street": "Test",
                "company_city": "Test",
                "company_zip": "12345",
                "company_country": "DE",
            },
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 403


# ─── Contact Folders Tests ───


class TestContactFoldersRoutes:
    """Tests for /api/v1/contact-folders — CRUD and edge cases."""

    @pytest.mark.asyncio
    async def test_list_contact_folders_empty_returns_200(self, client: AsyncClient, db_session):
        """GET /contact-folders returns 200 with empty list."""
        await seed_tenant_and_users(db_session)
        await login_client(client, "admin@tenanta.com")
        resp = await client.get("/api/v1/contact-folders", headers=ORIGIN_HEADER)
        assert resp.status_code == 200

    @pytest.mark.asyncio
    async def test_create_contact_folder_returns_201(self, client: AsyncClient, db_session):
        """POST /contact-folders creates a folder and returns 201."""
        await seed_tenant_and_users(db_session)
        await login_client(client, "admin@tenanta.com")
        resp = await client.post(
            "/api/v1/contact-folders",
            json={"name": "VIP Customers"},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 201
        data = resp.json()
        assert data["name"] == "VIP Customers"

    @pytest.mark.asyncio
    async def test_create_contact_folder_empty_name_returns_422(self, client: AsyncClient, db_session):
        """POST /contact-folders with empty name returns 422."""
        await seed_tenant_and_users(db_session)
        await login_client(client, "admin@tenanta.com")
        resp = await client.post(
            "/api/v1/contact-folders",
            json={"name": ""},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 422

    @pytest.mark.asyncio
    async def test_delete_contact_folder_not_found_returns_404(self, client: AsyncClient, db_session):
        """DELETE /contact-folders/{nonexistent_uuid} returns 404."""
        await seed_tenant_and_users(db_session)
        await login_client(client, "admin@tenanta.com")
        resp = await client.delete(
            "/api/v1/contact-folders/00000000-0000-0000-0000-000000000000",
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 404


# ─── Notifications Edge Cases ───


class TestNotificationsEdgeCases:
    """Edge case tests for /api/v1/notifications."""

    @pytest.mark.asyncio
    async def test_mark_notification_invalid_uuid_returns_400(self, client: AsyncClient, db_session):
        """PATCH /notifications/{invalid_uuid}/read returns 400."""
        await seed_tenant_and_users(db_session)
        await login_client(client, "admin@tenanta.com")
        resp = await client.patch(
            "/api/v1/notifications/not-a-uuid/read",
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code in (400, 404)

    @pytest.mark.asyncio
    async def test_mark_notification_not_found_returns_404(self, client: AsyncClient, db_session):
        """PATCH /notifications/{nonexistent_uuid}/read returns 404."""
        await seed_tenant_and_users(db_session)
        await login_client(client, "admin@tenanta.com")
        resp = await client.patch(
            "/api/v1/notifications/00000000-0000-0000-0000-000000000000/read",
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 404

    @pytest.mark.asyncio
    async def test_unread_count_returns_integer(self, client: AsyncClient, db_session):
        """GET /notifications/unread-count returns integer count."""
        await seed_tenant_and_users(db_session)
        await login_client(client, "admin@tenanta.com")
        resp = await client.get("/api/v1/notifications/unread-count", headers=ORIGIN_HEADER)
        assert resp.status_code == 200
        data = resp.json()
        assert isinstance(data["count"], int)
        assert data["count"] >= 0

    @pytest.mark.asyncio
    async def test_list_notification_types_returns_200(self, client: AsyncClient, db_session):
        """GET /notifications/types returns 200 with list."""
        await seed_tenant_and_users(db_session)
        await login_client(client, "admin@tenanta.com")
        resp = await client.get("/api/v1/notifications/types", headers=ORIGIN_HEADER)
        assert resp.status_code == 200


# ─── Entity History Tests ───


class TestEntityHistoryRoutes:
    """Tests for /api/v1/entity-history — query and edge cases."""

    @pytest.mark.asyncio
    async def test_get_entity_history_not_found_returns_200_empty(
        self, client: AsyncClient, db_session
    ):
        """GET /entity-history/{type}/{id} returns 200 with empty list for nonexistent entity."""
        await seed_tenant_and_users(db_session)
        await login_client(client, "admin@tenanta.com")
        resp = await client.get(
            "/api/v1/entity-history/contact/00000000-0000-0000-0000-000000000000",
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 200

    @pytest.mark.asyncio
    async def test_get_entity_history_invalid_uuid_returns_400(
        self, client: AsyncClient, db_session
    ):
        """GET /entity-history/{type}/{invalid_uuid} returns 400."""
        await seed_tenant_and_users(db_session)
        await login_client(client, "admin@tenanta.com")
        resp = await client.get(
            "/api/v1/entity-history/contact/not-a-uuid",
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code in (400, 422)


# ─── Multi-Tenant Isolation Tests ───


class TestMultiTenantIsolation:
    """Cross-tenant access tests — ensure data isolation between tenants."""

    @pytest.mark.asyncio
    async def test_cross_tenant_contact_access_returns_404(self, client: AsyncClient, db_session):
        """Admin A cannot access contacts from Tenant B."""
        seed = await seed_tenant_and_users(db_session)
        await login_client(client, "admin@tenanta.com")
        # Try to access company_b (belongs to tenant B) as admin_a
        resp = await client.get(
            f"/api/v1/contacts/{seed['company_b'].id}",
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 404

    @pytest.mark.asyncio
    async def test_cross_tenant_currency_isolation(self, client: AsyncClient, db_session):
        """Currencies created in Tenant A are not visible to Tenant B admin."""
        await seed_tenant_and_users(db_session)
        # Create currency as admin A
        await login_client(client, "admin@tenanta.com")
        resp = await client.post(
            "/api/v1/currencies",
            json={"code": "EUR", "name": "Euro", "symbol": "€"},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 201
        currency_id = resp.json()["id"]

        # Login as admin B — clear cookies to switch session
        client.cookies.clear()
        await login_client(client, "admin@tenantb.com")
        resp = await client.get("/api/v1/currencies", headers=ORIGIN_HEADER)
        assert resp.status_code == 200
        data = resp.json()
        # Currency from tenant A should not appear
        currency_ids = [c["id"] for c in data["items"]]
        assert currency_id not in currency_ids

    @pytest.mark.asyncio
    async def test_cross_tenant_sequence_isolation(self, client: AsyncClient, db_session):
        """Sequences created in Tenant A are not visible to Tenant B."""
        await seed_tenant_and_users(db_session)
        # Create sequence as admin A
        await login_client(client, "admin@tenanta.com")
        resp = await client.post(
            "/api/v1/sequences",
            json={"name": "invoice", "prefix": "RE-", "padding": 4},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 201

        # Login as admin B
        client.cookies.clear()
        await login_client(client, "admin@tenantb.com")
        resp = await client.get("/api/v1/sequences", headers=ORIGIN_HEADER)
        assert resp.status_code == 200
        data = resp.json()
        # No sequences from tenant A should be visible
        assert data["total"] == 0

    @pytest.mark.asyncio
    async def test_unauthenticated_access_returns_401(self, client: AsyncClient, db_session):
        """Unauthenticated requests to protected routes return 401."""
        await seed_tenant_and_users(db_session)
        # No login — direct access
        resp = await client.get("/api/v1/currencies", headers=ORIGIN_HEADER)
        assert resp.status_code in (401, 403)

        resp = await client.get("/api/v1/sequences", headers=ORIGIN_HEADER)
        assert resp.status_code in (401, 403)

        resp = await client.get("/api/v1/system-settings", headers=ORIGIN_HEADER)
        assert resp.status_code in (401, 403)

        resp = await client.get("/api/v1/notifications", headers=ORIGIN_HEADER)
        assert resp.status_code in (401, 403)
