"""
Tests for INDL-04 — Site Admin Enquiries Module.
Covers:
  - GET  /api/site-admin/enquiries            (list, filter, search, pagination)
  - GET  /api/site-admin/enquiries/new-count  (bell badge count)
  - PATCH /api/site-admin/enquiries/{id}      (status update)
  - POST  /api/public/contact                 (new fields: role, phone, type)
  - POST  /api/public/book-a-call             (new endpoint)
  - Permission gate: non-site_admin gets 403
"""
import uuid

import pytest
import pytest_asyncio
from httpx import AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession

from src.apps.auth.models.user import User
from src.apps.public.models.website_enquiry import WebsiteEnquiry
from src.core.security import build_token_payload, create_access_token, hash_password


# ── Helpers ───────────────────────────────────────────────────────────────────


async def _make_sa_headers(db: AsyncSession) -> dict:
    """Create a site_admin user and return auth headers."""
    user = User(
        tenant_id=None,
        email=f"sa-enq-{uuid.uuid4().hex[:8]}@indelis.com",
        password_hash=hash_password("SiteAdmin123!"),
        first_name="Site",
        last_name="Admin",
        role="site_admin",
        status="active",
    )
    db.add(user)
    await db.flush()
    token = create_access_token(build_token_payload(user))
    return {"Authorization": f"Bearer {token}"}


async def _make_manager_headers(db: AsyncSession) -> dict:
    """Create a regular manager user (non-site_admin) and return auth headers."""
    from src.apps.tenants.models.account import Account
    account = Account(
        organization_name="Perm Test Cem",
        subdomain=f"permcem-{uuid.uuid4().hex[:6]}",
        contact_email=f"admin-{uuid.uuid4().hex[:6]}@permtest.ca",
        plan="starter",
        status="active",
    )
    db.add(account)
    await db.flush()

    user = User(
        tenant_id=account.id,
        email=f"mgr-{uuid.uuid4().hex[:8]}@test.ca",
        password_hash=hash_password("Manager123!"),
        first_name="Regular",
        last_name="Manager",
        role="manager",
        status="active",
    )
    db.add(user)
    await db.flush()
    token = create_access_token(build_token_payload(user))
    return {"Authorization": f"Bearer {token}"}


async def _create_enquiry(
    db: AsyncSession,
    *,
    name: str = "Alice Test",
    email: str = None,
    organization: str = "Test Org",
    status: str = "new",
    enq_type: str = "contact",
    day: str = None,
    time: str = None,
    message: str = "Test message",
    phone: str = None,
    role: str = None,
) -> WebsiteEnquiry:
    email = email or f"enq-{uuid.uuid4().hex[:8]}@test.ca"
    enq = WebsiteEnquiry(
        name=name,
        email=email,
        organization=organization,
        message=message,
        status=status,
        type=enq_type,
        day=day,
        time=time,
        phone=phone,
        role=role,
    )
    db.add(enq)
    await db.flush()
    return enq


# ── T-01: GET /enquiries — empty list ─────────────────────────────────────────


@pytest.mark.asyncio
async def test_list_enquiries_empty(client: AsyncClient, db_session: AsyncSession):
    """T-01: Returns empty paginated list when no enquiries exist."""
    headers = await _make_sa_headers(db_session)
    resp = await client.get("/api/site-admin/enquiries", headers=headers)
    assert resp.status_code == 200
    body = resp.json()
    assert body["success"] is True
    assert isinstance(body["data"], list)
    assert body["total"] == 0


# ── T-02: GET /enquiries — basic list ────────────────────────────────────────


@pytest.mark.asyncio
async def test_list_enquiries_returns_items(client: AsyncClient, db_session: AsyncSession):
    """T-02: Returns created enquiries in newest-first order."""
    headers = await _make_sa_headers(db_session)
    await _create_enquiry(db_session, name="Alpha One", status="new", enq_type="contact")
    await _create_enquiry(db_session, name="Beta Two", status="contacted", enq_type="call", day="Mon", time="AM")

    resp = await client.get("/api/site-admin/enquiries", headers=headers)
    assert resp.status_code == 200
    body = resp.json()
    assert body["total"] == 2
    items = body["data"]
    assert len(items) == 2
    # newest first — last created is Beta Two
    assert items[0]["name"] == "Beta Two"
    assert items[1]["name"] == "Alpha One"


# ── T-03: response shape includes INDL-04 fields ─────────────────────────────


@pytest.mark.asyncio
async def test_enquiry_response_shape(client: AsyncClient, db_session: AsyncSession):
    """T-03: Response items include new INDL-04 fields (type, role, phone, day, time)."""
    headers = await _make_sa_headers(db_session)
    await _create_enquiry(
        db_session,
        name="Sandra Lim",
        enq_type="call",
        day="Next week",
        time="Morning (9 AM – 12 PM)",
        phone="(604) 555-0143",
        role="Director",
    )

    resp = await client.get("/api/site-admin/enquiries", headers=headers)
    body = resp.json()
    item = body["data"][0]
    assert item["type"] == "call"
    assert item["day"] == "Next week"
    assert item["time"] == "Morning (9 AM – 12 PM)"
    assert item["phone"] == "(604) 555-0143"
    assert item["role"] == "Director"


# ── T-04: Filter by type ──────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_filter_by_type_call(client: AsyncClient, db_session: AsyncSession):
    """T-04: type=call returns only call-type enquiries."""
    headers = await _make_sa_headers(db_session)
    await _create_enquiry(db_session, enq_type="contact")
    await _create_enquiry(db_session, enq_type="call", day="Fri", time="PM")

    resp = await client.get("/api/site-admin/enquiries?type=call", headers=headers)
    body = resp.json()
    assert body["total"] == 1
    assert body["data"][0]["type"] == "call"


@pytest.mark.asyncio
async def test_filter_by_type_contact(client: AsyncClient, db_session: AsyncSession):
    """T-04: type=contact returns only contact-type enquiries."""
    headers = await _make_sa_headers(db_session)
    await _create_enquiry(db_session, enq_type="contact")
    await _create_enquiry(db_session, enq_type="call", day="Fri", time="PM")

    resp = await client.get("/api/site-admin/enquiries?type=contact", headers=headers)
    body = resp.json()
    assert body["total"] == 1
    assert body["data"][0]["type"] == "contact"


# ── T-05: Filter by status ────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_filter_by_status(client: AsyncClient, db_session: AsyncSession):
    """T-05: status filter returns only matching rows."""
    headers = await _make_sa_headers(db_session)
    await _create_enquiry(db_session, status="new")
    await _create_enquiry(db_session, status="qualified")
    await _create_enquiry(db_session, status="closed")

    resp = await client.get("/api/site-admin/enquiries?status=new", headers=headers)
    body = resp.json()
    assert body["total"] == 1
    assert body["data"][0]["status"] == "new"


# ── T-06: Compose filters ─────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_compose_type_and_status_filters(client: AsyncClient, db_session: AsyncSession):
    """T-06: type + status filters compose correctly (AND logic)."""
    headers = await _make_sa_headers(db_session)
    await _create_enquiry(db_session, enq_type="call", status="new", day="Mon", time="AM")
    await _create_enquiry(db_session, enq_type="contact", status="new")
    await _create_enquiry(db_session, enq_type="call", status="qualified", day="Tue", time="PM")

    resp = await client.get("/api/site-admin/enquiries?type=call&status=new", headers=headers)
    body = resp.json()
    assert body["total"] == 1
    assert body["data"][0]["type"] == "call"
    assert body["data"][0]["status"] == "new"


# ── T-07: Search ──────────────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_search_by_name(client: AsyncClient, db_session: AsyncSession):
    """T-07: q search matches name field."""
    headers = await _make_sa_headers(db_session)
    await _create_enquiry(db_session, name="Sandra Lim")
    await _create_enquiry(db_session, name="David Okafor")

    resp = await client.get("/api/site-admin/enquiries?q=Sandra", headers=headers)
    body = resp.json()
    assert body["total"] == 1
    assert body["data"][0]["name"] == "Sandra Lim"


@pytest.mark.asyncio
async def test_search_by_email(client: AsyncClient, db_session: AsyncSession):
    """T-07: q search matches email field."""
    headers = await _make_sa_headers(db_session)
    await _create_enquiry(db_session, email="specific@hillcrest.com")
    await _create_enquiry(db_session, email="other@greenfield.org")

    resp = await client.get("/api/site-admin/enquiries?q=hillcrest", headers=headers)
    body = resp.json()
    assert body["total"] == 1
    assert "hillcrest" in body["data"][0]["email"]


@pytest.mark.asyncio
async def test_search_by_message(client: AsyncClient, db_session: AsyncSession):
    """T-07: q search matches message field."""
    headers = await _make_sa_headers(db_session)
    await _create_enquiry(db_session, message="Interested in AI record extraction")
    await _create_enquiry(db_session, message="General pricing inquiry")

    resp = await client.get("/api/site-admin/enquiries?q=AI+record", headers=headers)
    body = resp.json()
    assert body["total"] == 1


@pytest.mark.asyncio
async def test_search_no_match_returns_empty(client: AsyncClient, db_session: AsyncSession):
    """T-07: No-match search returns empty list."""
    headers = await _make_sa_headers(db_session)
    await _create_enquiry(db_session)

    resp = await client.get("/api/site-admin/enquiries?q=zzznomatch", headers=headers)
    body = resp.json()
    assert body["total"] == 0
    assert body["data"] == []


# ── T-08: Pagination ──────────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_pagination(client: AsyncClient, db_session: AsyncSession):
    """T-08: skip/limit pagination works correctly."""
    headers = await _make_sa_headers(db_session)
    for i in range(5):
        await _create_enquiry(db_session, name=f"Enquirer {i}")

    resp = await client.get("/api/site-admin/enquiries?skip=0&limit=3", headers=headers)
    body = resp.json()
    assert body["total"] == 5
    assert len(body["data"]) == 3

    resp2 = await client.get("/api/site-admin/enquiries?skip=3&limit=3", headers=headers)
    body2 = resp2.json()
    assert body2["total"] == 5
    assert len(body2["data"]) == 2


# ── T-09: New count (bell badge) ──────────────────────────────────────────────


@pytest.mark.asyncio
async def test_new_count(client: AsyncClient, db_session: AsyncSession):
    """T-09: /enquiries/new-count returns count of new enquiries."""
    headers = await _make_sa_headers(db_session)
    await _create_enquiry(db_session, status="new")
    await _create_enquiry(db_session, status="new")
    await _create_enquiry(db_session, status="contacted")

    resp = await client.get("/api/site-admin/enquiries/new-count", headers=headers)
    assert resp.status_code == 200
    body = resp.json()
    assert body["data"]["count"] == 2


# ── T-10: PATCH status update ─────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_update_status_valid(client: AsyncClient, db_session: AsyncSession):
    """T-10: PATCH updates enquiry status and returns updated record."""
    headers = await _make_sa_headers(db_session)
    enq = await _create_enquiry(db_session, status="new")

    resp = await client.patch(
        f"/api/site-admin/enquiries/{enq.id}",
        json={"status": "contacted"},
        headers=headers,
    )
    assert resp.status_code == 200
    body = resp.json()
    assert body["success"] is True
    assert body["data"]["status"] == "contacted"
    assert body["message"] == "Status updated"


@pytest.mark.asyncio
async def test_update_status_all_valid_values(client: AsyncClient, db_session: AsyncSession):
    """T-10: All four valid status values are accepted."""
    headers = await _make_sa_headers(db_session)
    for status in ["new", "contacted", "qualified", "closed"]:
        enq = await _create_enquiry(db_session, status="new")
        resp = await client.patch(
            f"/api/site-admin/enquiries/{enq.id}",
            json={"status": status},
            headers=headers,
        )
        assert resp.status_code == 200, f"Expected 200 for status={status}"


@pytest.mark.asyncio
async def test_update_status_invalid_returns_422(client: AsyncClient, db_session: AsyncSession):
    """T-10: Invalid status value returns 422."""
    headers = await _make_sa_headers(db_session)
    enq = await _create_enquiry(db_session)
    resp = await client.patch(
        f"/api/site-admin/enquiries/{enq.id}",
        json={"status": "invalid_status"},
        headers=headers,
    )
    assert resp.status_code == 422


@pytest.mark.asyncio
async def test_update_status_not_found_returns_404(client: AsyncClient, db_session: AsyncSession):
    """T-10: PATCH on non-existent ID returns 404."""
    headers = await _make_sa_headers(db_session)
    fake_id = uuid.uuid4()
    resp = await client.patch(
        f"/api/site-admin/enquiries/{fake_id}",
        json={"status": "contacted"},
        headers=headers,
    )
    assert resp.status_code == 404


# ── T-11: Permission gate ─────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_list_enquiries_forbidden_non_site_admin(client: AsyncClient, db_session: AsyncSession):
    """T-11: Non-site_admin role gets 403."""
    headers = await _make_manager_headers(db_session)
    resp = await client.get("/api/site-admin/enquiries", headers=headers)
    assert resp.status_code == 403


@pytest.mark.asyncio
async def test_patch_enquiry_forbidden_non_site_admin(client: AsyncClient, db_session: AsyncSession):
    """T-11: PATCH also returns 403 for non-site_admin."""
    headers = await _make_manager_headers(db_session)
    fake_id = uuid.uuid4()
    resp = await client.patch(
        f"/api/site-admin/enquiries/{fake_id}",
        json={"status": "contacted"},
        headers=headers,
    )
    assert resp.status_code == 403


@pytest.mark.asyncio
async def test_list_enquiries_unauthenticated(client: AsyncClient):
    """T-11: Unauthenticated request returns 401."""
    resp = await client.get("/api/site-admin/enquiries")
    assert resp.status_code == 401


# ── T-12: POST /api/public/contact — new fields ───────────────────────────────


@pytest.mark.asyncio
async def test_public_contact_persists_role_and_phone(client: AsyncClient, db_session: AsyncSession):
    """T-12: POST /api/public/contact persists role and phone fields."""
    sa_headers = await _make_sa_headers(db_session)
    payload = {
        "name": "David Okafor",
        "email": "dokafor@greenfield.org",
        "organization": "Greenfield Memorial",
        "role": "Administrator",
        "phone": "(905) 555-0337",
        "message": "Test message",
        "type": "contact",
    }
    resp = await client.post("/api/public/contact", json=payload)
    assert resp.status_code == 201
    enq_id = resp.json()["data"]["id"]

    list_resp = await client.get("/api/site-admin/enquiries", headers=sa_headers)
    items = list_resp.json()["data"]
    created = next((i for i in items if i["id"] == enq_id), None)
    assert created is not None
    assert created["role"] == "Administrator"
    assert created["phone"] == "(905) 555-0337"
    assert created["type"] == "contact"


@pytest.mark.asyncio
async def test_public_contact_default_type_is_contact(client: AsyncClient, db_session: AsyncSession):
    """T-12: When type is omitted, defaults to 'contact'."""
    sa_headers = await _make_sa_headers(db_session)
    payload = {
        "name": "Jane Default",
        "email": "jane@default.org",
        "message": "No type field",
    }
    resp = await client.post("/api/public/contact", json=payload)
    assert resp.status_code == 201
    enq_id = resp.json()["data"]["id"]

    list_resp = await client.get("/api/site-admin/enquiries", headers=sa_headers)
    items = list_resp.json()["data"]
    created = next((i for i in items if i["id"] == enq_id), None)
    assert created["type"] == "contact"


# ── T-13: POST /api/public/book-a-call ───────────────────────────────────────


@pytest.mark.asyncio
async def test_book_a_call_creates_call_enquiry(client: AsyncClient, db_session: AsyncSession):
    """T-13: POST /api/public/book-a-call creates a call-type enquiry with day/time."""
    sa_headers = await _make_sa_headers(db_session)
    payload = {
        "name": "Sandra Lim",
        "email": "slim@hillcrest.com",
        "organization": "Hillcrest Funeral Group",
        "role": "Director",
        "phone": "(604) 555-0143",
        "day": "Next week",
        "time": "Morning (9 AM – 12 PM)",
        "message": "We operate five cemeteries.",
    }
    resp = await client.post("/api/public/book-a-call", json=payload)
    assert resp.status_code == 201
    body = resp.json()
    assert body["success"] is True
    enq_id = body["data"]["id"]
    assert enq_id is not None

    list_resp = await client.get("/api/site-admin/enquiries", headers=sa_headers)
    items = list_resp.json()["data"]
    created = next((i for i in items if i["id"] == enq_id), None)
    assert created is not None
    assert created["type"] == "call"
    assert created["day"] == "Next week"
    assert created["time"] == "Morning (9 AM – 12 PM)"
    assert created["phone"] == "(604) 555-0143"
    assert created["role"] == "Director"
    assert created["status"] == "new"


@pytest.mark.asyncio
async def test_book_a_call_requires_day_and_time(client: AsyncClient):
    """T-13: day and time are required for book-a-call."""
    payload = {"name": "No Day", "email": "noday@test.com"}
    resp = await client.post("/api/public/book-a-call", json=payload)
    assert resp.status_code == 422


@pytest.mark.asyncio
async def test_book_a_call_honeypot_discards(client: AsyncClient):
    """T-13: Honeypot field filled → silently appears successful, no record created."""
    payload = {
        "name": "Bot",
        "email": "bot@spam.com",
        "day": "Monday",
        "time": "Morning",
        "website": "http://spam.example.com",
    }
    resp = await client.post("/api/public/book-a-call", json=payload)
    assert resp.status_code == 201
    assert resp.json()["data"]["id"] is None


# ── T-14: PII not leaked in responses ────────────────────────────────────────


@pytest.mark.asyncio
async def test_ip_and_user_agent_not_in_response(client: AsyncClient, db_session: AsyncSession):
    """T-14: ip_address and user_agent are stored but never returned."""
    headers = await _make_sa_headers(db_session)
    await _create_enquiry(db_session)

    resp = await client.get("/api/site-admin/enquiries", headers=headers)
    item = resp.json()["data"][0]
    assert "ip_address" not in item
    assert "user_agent" not in item
