"""
Tests for the Pages CMS (INDL-31): GET/PUT /api/v1/pages/{slug} and
GET /api/public/pages/{slug}.
Covers T-01, T-02, T-03, T-04, T-05, T-06, T-07, T-08, T-14, T-15,
and security cases SEC-01, SEC-05, SEC-06.
"""
import pytest
import pytest_asyncio
from httpx import AsyncClient
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from src.apps.pages.models.page_content import TenantPageContent
from src.core.security import create_access_token, hash_password


VALID_GLOBAL = {
    "phone_main": "(613) 555-0142",
    "email": "office@riverside-memorial.ca",
    "address_street": "1247 Riverside Drive",
    "address_city": "Ottawa",
    "address_province": "ON",
    "address_postal": "K1H 8L9",
    "hours_grounds": "7 am - sunset, daily",
    "hours_office_weekday": "Mon-Fri 8:30 am - 4:30 pm",
    "total_hectares": 32.0,
    "lat": 45.377,
    "lng": -75.6975,
}


async def _make_user_token(db_session: AsyncSession, tenant_id, role: str) -> str:
    from src.apps.auth.models.user import User

    user = User(
        tenant_id=tenant_id,
        email=f"{role}@testcemetery.com",
        password_hash=hash_password("TestPassword123"),
        first_name=role.capitalize(),
        last_name="User",
        role=role,
        status="active",
    )
    db_session.add(user)
    await db_session.flush()
    payload = {
        "sub": str(user.id), "tenant_id": str(tenant_id), "role": role,
        "iss": "indelis-api", "aud": "indelis-client",
    }
    return create_access_token(payload)


@pytest_asyncio.fixture
async def manager_headers(db_session: AsyncSession, test_account):
    token = await _make_user_token(db_session, test_account.id, "manager")
    return {"Authorization": f"Bearer {token}"}


@pytest_asyncio.fixture
async def staff_headers(db_session: AsyncSession, test_account):
    token = await _make_user_token(db_session, test_account.id, "staff")
    return {"Authorization": f"Bearer {token}"}


@pytest_asyncio.fixture
async def view_only_headers(db_session: AsyncSession, test_account):
    token = await _make_user_token(db_session, test_account.id, "view_only")
    return {"Authorization": f"Bearer {token}"}


# ── T-01 / T-02: public GET ──────────────────────────────────────────────────

@pytest.mark.asyncio
async def test_public_get_page_content_seeded(
    client: AsyncClient, db_session: AsyncSession, test_account
):
    """T-01: GET /api/public/pages/global with valid tenant returns seeded content."""
    db_session.add(
        TenantPageContent(tenant_id=test_account.id, page_slug="global", content=VALID_GLOBAL)
    )
    await db_session.flush()

    response = await client.get(
        "/api/public/pages/global", headers={"X-Tenant-ID": str(test_account.id)}
    )
    assert response.status_code == 200
    body = response.json()
    assert body["data"]["content"]["email"] == "office@riverside-memorial.ca"


@pytest.mark.asyncio
async def test_public_get_page_content_no_row(client: AsyncClient, test_account):
    """T-02: GET with no DB row → 200, content: {}."""
    response = await client.get(
        "/api/public/pages/find", headers={"X-Tenant-ID": str(test_account.id)}
    )
    assert response.status_code == 200
    assert response.json()["data"]["content"] == {}


@pytest.mark.asyncio
async def test_public_get_page_content_invalid_slug(client: AsyncClient, test_account):
    """T-03: GET /api/public/pages/invalid-slug falls through to marketing CmsPage lookup → 404."""
    response = await client.get(
        "/api/public/pages/invalid-slug", headers={"X-Tenant-ID": str(test_account.id)}
    )
    assert response.status_code == 404


# ── SEC-01: unknown tenant never leaks another tenant's data ────────────────

@pytest.mark.asyncio
async def test_public_get_page_content_unknown_tenant(client: AsyncClient):
    """SEC-01: unresolvable tenant slug/id → 404, never 500 or another tenant's data."""
    response = await client.get(
        "/api/public/pages/global",
        headers={"X-Tenant-Slug": "definitely-not-a-real-cemetery"},
    )
    assert response.status_code == 404


@pytest.mark.asyncio
async def test_public_get_page_content_no_tenant_context(client: AsyncClient):
    response = await client.get("/api/public/pages/global")
    assert response.status_code == 404


# ── T-04 / T-05 / T-06 / T-07: admin PUT validation ──────────────────────────

@pytest.mark.asyncio
async def test_admin_put_global_valid(
    client: AsyncClient, db_session: AsyncSession, manager_headers, test_account
):
    """T-04: PUT with all valid fields (manager role) → 200; row upserted."""
    headers = {**manager_headers, "X-Tenant-ID": str(test_account.id)}
    response = await client.put("/api/v1/pages/global", json=VALID_GLOBAL, headers=headers)
    assert response.status_code == 200
    body = response.json()
    assert body["data"]["updated_by_name"] == "Manager User"

    result = await db_session.execute(
        select(TenantPageContent).where(
            TenantPageContent.tenant_id == test_account.id,
            TenantPageContent.page_slug == "global",
        )
    )
    row = result.scalar_one_or_none()
    assert row is not None
    assert row.content["email"] == "office@riverside-memorial.ca"


@pytest.mark.asyncio
async def test_admin_put_global_invalid_postal(
    client: AsyncClient, manager_headers, test_account
):
    """T-05: PUT with invalid postal code → 422."""
    payload = {**VALID_GLOBAL, "address_postal": "BADCODE"}
    headers = {**manager_headers, "X-Tenant-ID": str(test_account.id)}
    response = await client.put("/api/v1/pages/global", json=payload, headers=headers)
    assert response.status_code == 422


@pytest.mark.asyncio
async def test_admin_put_global_legacy_founding_year_ignored(
    client: AsyncClient, manager_headers, test_account
):
    """T-06: founding_year moved to accounts.established_year (Settings) — a legacy
    founding_year key in the payload is silently dropped, not rejected as an
    unknown field, so old admin clients/cached forms don't start 422ing."""
    payload = {**VALID_GLOBAL, "founding_year": 1500}
    headers = {**manager_headers, "X-Tenant-ID": str(test_account.id)}
    response = await client.put("/api/v1/pages/global", json=payload, headers=headers)
    assert response.status_code == 200
    assert "founding_year" not in response.json()["data"]["content"]


@pytest.mark.asyncio
async def test_admin_put_global_bad_latitude(
    client: AsyncClient, manager_headers, test_account
):
    """T-07: PUT with lat = 91 → 422 (out of range)."""
    payload = {**VALID_GLOBAL, "lat": 91}
    headers = {**manager_headers, "X-Tenant-ID": str(test_account.id)}
    response = await client.put("/api/v1/pages/global", json=payload, headers=headers)
    assert response.status_code == 422


# ── T-08 / SEC-05: role matrix ───────────────────────────────────────────────

@pytest.mark.asyncio
async def test_admin_put_global_staff_forbidden(
    client: AsyncClient, staff_headers, test_account
):
    """T-08 / SEC-05: PUT with staff role (not manager) → 403."""
    headers = {**staff_headers, "X-Tenant-ID": str(test_account.id)}
    response = await client.put("/api/v1/pages/global", json=VALID_GLOBAL, headers=headers)
    assert response.status_code == 403


@pytest.mark.asyncio
async def test_admin_put_global_view_only_forbidden(
    client: AsyncClient, view_only_headers, test_account
):
    """SEC-05: PUT with view_only role → 403."""
    headers = {**view_only_headers, "X-Tenant-ID": str(test_account.id)}
    response = await client.put("/api/v1/pages/global", json=VALID_GLOBAL, headers=headers)
    assert response.status_code == 403


@pytest.mark.asyncio
async def test_admin_get_global_view_only_forbidden(
    client: AsyncClient, view_only_headers, test_account
):
    """view_only is below the staff floor required for GET."""
    headers = {**view_only_headers, "X-Tenant-ID": str(test_account.id)}
    response = await client.get("/api/v1/pages/global", headers=headers)
    assert response.status_code == 403


@pytest.mark.asyncio
async def test_admin_get_global_staff_allowed(
    client: AsyncClient, staff_headers, test_account
):
    headers = {**staff_headers, "X-Tenant-ID": str(test_account.id)}
    response = await client.get("/api/v1/pages/global", headers=headers)
    assert response.status_code == 200


# ── SEC-06: extra="forbid" ────────────────────────────────────────────────────

@pytest.mark.asyncio
async def test_admin_put_global_extra_field_rejected(
    client: AsyncClient, manager_headers, test_account
):
    """SEC-06: extra unknown field → 422, extra='forbid' enforced."""
    payload = {**VALID_GLOBAL, "_internal_flag": True}
    headers = {**manager_headers, "X-Tenant-ID": str(test_account.id)}
    response = await client.put("/api/v1/pages/global", json=payload, headers=headers)
    assert response.status_code == 422


# ── T-14: tenant isolation ────────────────────────────────────────────────────

@pytest.mark.asyncio
async def test_admin_get_different_tenants_isolated(
    client: AsyncClient, db_session: AsyncSession, manager_headers, test_account
):
    """T-14: GET /api/v1/pages/find from a different tenant returns that tenant's content only."""
    from src.apps.tenants.models.account import Account

    other = Account(
        organization_name="Other Cemetery",
        subdomain="othercemetery",
        contact_email="admin@othercemetery.com",
        plan="starter",
        status="active",
    )
    db_session.add(other)
    await db_session.flush()

    db_session.add(TenantPageContent(
        tenant_id=other.id, page_slug="find",
        content={"hero_headline": "Other cemetery headline", "hero_subtitle": "x" * 20},
    ))
    await db_session.flush()

    headers = {**manager_headers, "X-Tenant-ID": str(test_account.id)}
    response = await client.get("/api/v1/pages/find", headers=headers)
    assert response.status_code == 200
    assert response.json()["data"]["content"] == {}


# ── T-15: seeder idempotency (unit-level, not a subprocess) ──────────────────

@pytest.mark.asyncio
async def test_seed_upsert_idempotent(db_session: AsyncSession, test_account):
    """T-15: seeding the same tenant/slug twice updates in place, no duplicate rows."""
    payload_v1 = {**VALID_GLOBAL}
    payload_v2 = {**VALID_GLOBAL, "phone_main": "(613) 555-9999"}

    from src.apps.pages.services.page_content_service import PageContentService

    await PageContentService.upsert(db_session, test_account.id, "global", payload_v1, None)
    await PageContentService.upsert(db_session, test_account.id, "global", payload_v2, None)

    result = await db_session.execute(
        select(TenantPageContent).where(
            TenantPageContent.tenant_id == test_account.id,
            TenantPageContent.page_slug == "global",
        )
    )
    rows = result.scalars().all()
    assert len(rows) == 1
    assert rows[0].content["phone_main"] == "(613) 555-9999"
