"""
Tests for INDL-06 — Enquiry Workflow Transition.
Covers all 20 test cases from the PRD:
  T-01 .. T-20  state machine, audit logs, auto-close, new-count, edge cases.
"""
import uuid
from datetime import datetime, timedelta, timezone

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

from src.apps.auth.models.user import User
from src.apps.public.models.website_enquiry import WebsiteEnquiry
from src.apps.site_admin.models.enquiry_audit_log import EnquiryAuditLog
from src.apps.site_admin.services.enquiry_service import EnquiryService
from src.core.security import build_token_payload, create_access_token, hash_password


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


async def _make_sa_user(db: AsyncSession) -> User:
    user = User(
        tenant_id=None,
        email=f"sa-wf-{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()
    return user


async def _make_sa_headers(db: AsyncSession) -> tuple[dict, User]:
    user = await _make_sa_user(db)
    token = create_access_token(build_token_payload(user))
    return {"Authorization": f"Bearer {token}"}, user


async def _create_enquiry(
    db: AsyncSession,
    *,
    status: str = "new",
    name: str = "Test Contact",
    email: str = None,
    organization: str = "Test Org",
    cemetery_type: str = "Municipal cemetery",
    qualified_email_sent_at: datetime = None,
    signup_completed_at: datetime = None,
) -> WebsiteEnquiry:
    enq = WebsiteEnquiry(
        name=name,
        email=email or f"enq-{uuid.uuid4().hex[:8]}@test.ca",
        organization=organization,
        cemetery_type=cemetery_type,
        message="Test message",
        status=status,
        source="marketing",
        type="contact",
        qualified_email_sent_at=qualified_email_sent_at,
        signup_completed_at=signup_completed_at,
    )
    db.add(enq)
    await db.flush()
    return enq


# ── T-01: Create enquiry → status = new ──────────────────────────────────────


@pytest.mark.asyncio
async def test_t01_create_enquiry_starts_new(client: AsyncClient, db_session: AsyncSession):
    """T-01: Admin-created enquiry always starts at status='new'."""
    headers, _ = await _make_sa_headers(db_session)
    resp = await client.post(
        "/api/site-admin/enquiries",
        json={"name": "Alice", "email": "alice@test.ca", "type": "contact"},
        headers=headers,
    )
    assert resp.status_code == 201
    data = resp.json()["data"]
    assert data["status"] == "new"
    assert data["name"] == "Alice"


# ── T-02: mark-contacted on new → contacted ───────────────────────────────────


@pytest.mark.asyncio
async def test_t02_mark_contacted_manual(client: AsyncClient, db_session: AsyncSession):
    """T-02: POST mark-contacted on 'new' enquiry → status=contacted, contact_method=manual."""
    headers, _ = await _make_sa_headers(db_session)
    enq = await _create_enquiry(db_session, status="new")

    resp = await client.post(
        f"/api/site-admin/enquiries/{enq.id}/mark-contacted",
        headers=headers,
    )
    assert resp.status_code == 200
    data = resp.json()["data"]
    assert data["status"] == "contacted"
    assert data["contact_method"] == "manual"
    assert data["contacted_at"] is not None


# ── T-03: mark-contacted on already-contacted → 409 ──────────────────────────


@pytest.mark.asyncio
async def test_t03_mark_contacted_already_contacted_409(client: AsyncClient, db_session: AsyncSession):
    """T-03: POST mark-contacted on already 'contacted' enquiry → HTTP 409."""
    headers, _ = await _make_sa_headers(db_session)
    enq = await _create_enquiry(db_session, status="contacted")

    resp = await client.post(
        f"/api/site-admin/enquiries/{enq.id}/mark-contacted",
        headers=headers,
    )
    assert resp.status_code == 409


# ── T-04: send-contact-reply on new → contacted email_reply ──────────────────


@pytest.mark.asyncio
async def test_t04_send_contact_reply(client: AsyncClient, db_session: AsyncSession):
    """T-04: POST send-contact-reply on 'new' enquiry → status=contacted, contact_method=email_reply."""
    headers, _ = await _make_sa_headers(db_session)
    enq = await _create_enquiry(db_session, status="new")

    resp = await client.post(
        f"/api/site-admin/enquiries/{enq.id}/send-contact-reply",
        json={"message": "Hello, thank you for your enquiry!"},
        headers=headers,
    )
    assert resp.status_code == 200
    data = resp.json()["data"]
    assert data["status"] == "contacted"
    assert data["contact_method"] == "email_reply"
    assert data["contacted_at"] is not None


# ── T-05: mark-contacted after email reply → 409 ─────────────────────────────


@pytest.mark.asyncio
async def test_t05_mark_contacted_after_email_reply_409(client: AsyncClient, db_session: AsyncSession):
    """T-05: POST mark-contacted after send-contact-reply already ran → 409."""
    headers, _ = await _make_sa_headers(db_session)
    enq = await _create_enquiry(db_session, status="contacted")
    enq.contact_method = "email_reply"
    await db_session.flush()

    resp = await client.post(
        f"/api/site-admin/enquiries/{enq.id}/mark-contacted",
        headers=headers,
    )
    assert resp.status_code == 409


# ── T-06: qualify on new (skip contacted) → 422 ──────────────────────────────


@pytest.mark.asyncio
async def test_t06_qualify_on_new_skips_contacted_422(client: AsyncClient, db_session: AsyncSession):
    """T-06: POST qualify on 'new' enquiry (skips contacted) → HTTP 422."""
    headers, _ = await _make_sa_headers(db_session)
    enq = await _create_enquiry(db_session, status="new")

    resp = await client.post(
        f"/api/site-admin/enquiries/{enq.id}/qualify",
        headers=headers,
    )
    assert resp.status_code == 422


# ── T-07: qualify on contacted → qualified ────────────────────────────────────


@pytest.mark.asyncio
async def test_t07_qualify_contacted_enquiry(client: AsyncClient, db_session: AsyncSession):
    """T-07: POST qualify on 'contacted' → status=qualified, qualified_at set, qualified_email_sent_at set."""
    headers, _ = await _make_sa_headers(db_session)
    enq = await _create_enquiry(db_session, status="contacted")

    resp = await client.post(
        f"/api/site-admin/enquiries/{enq.id}/qualify",
        headers=headers,
    )
    assert resp.status_code == 200
    data = resp.json()["data"]
    assert data["status"] == "qualified"
    assert data["qualified_at"] is not None
    assert data["qualified_email_sent_at"] is not None


# ── T-08: qualify twice → 409 ────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_t08_qualify_twice_409(client: AsyncClient, db_session: AsyncSession):
    """T-08: POST qualify twice on same enquiry → HTTP 409."""
    headers, _ = await _make_sa_headers(db_session)
    enq = await _create_enquiry(db_session, status="qualified")

    resp = await client.post(
        f"/api/site-admin/enquiries/{enq.id}/qualify",
        headers=headers,
    )
    assert resp.status_code == 409


# ── T-09: manual close allowed from new/contacted, rejected from qualified ──


@pytest.mark.asyncio
async def test_t09_manual_close_from_qualified_rejected(client: AsyncClient, db_session: AsyncSession):
    """T-09: Manually closing a 'qualified' enquiry (terminal-bound) is rejected."""
    headers, _ = await _make_sa_headers(db_session)
    enq = await _create_enquiry(db_session, status="qualified")

    resp = await client.post(
        f"/api/site-admin/enquiries/{enq.id}/close",
        headers=headers,
    )
    assert resp.status_code == 422


@pytest.mark.asyncio
async def test_t09b_manual_close_from_new_and_contacted_allowed(client: AsyncClient, db_session: AsyncSession):
    """T-09b: 'Not Interested' manual close succeeds from 'new' and 'contacted' statuses."""
    headers, _ = await _make_sa_headers(db_session)

    enq_new = await _create_enquiry(db_session, status="new")
    resp = await client.post(
        f"/api/site-admin/enquiries/{enq_new.id}/close",
        headers=headers,
    )
    assert resp.status_code == 200
    assert resp.json()["data"]["status"] == "closed"

    enq_contacted = await _create_enquiry(db_session, status="contacted")
    resp = await client.post(
        f"/api/site-admin/enquiries/{enq_contacted.id}/close",
        headers=headers,
    )
    assert resp.status_code == 200
    assert resp.json()["data"]["status"] == "closed"


# ── T-10: record-signup-completed on qualified ────────────────────────────────


@pytest.mark.asyncio
async def test_t10_record_signup_completed(client: AsyncClient, db_session: AsyncSession):
    """T-10: POST record-signup-completed → signup_completed_at set, status stays 'qualified'."""
    enq = await _create_enquiry(db_session, status="qualified")

    resp = await client.post(
        "/api/site-admin/enquiries/record-signup-completed",
        json={"enquiry_id": str(enq.id)},
    )
    assert resp.status_code == 200
    data = resp.json()["data"]
    assert data["status"] == "qualified"
    assert data["signup_completed_at"] is not None


# ── T-11: record-signup-completed idempotent ──────────────────────────────────


@pytest.mark.asyncio
async def test_t11_record_signup_completed_idempotent(client: AsyncClient, db_session: AsyncSession):
    """T-11: POST record-signup-completed twice → idempotent, HTTP 200 both times."""
    enq = await _create_enquiry(db_session, status="qualified")

    resp1 = await client.post(
        "/api/site-admin/enquiries/record-signup-completed",
        json={"enquiry_id": str(enq.id)},
    )
    assert resp1.status_code == 200
    first_ts = resp1.json()["data"]["signup_completed_at"]

    resp2 = await client.post(
        "/api/site-admin/enquiries/record-signup-completed",
        json={"enquiry_id": str(enq.id)},
    )
    assert resp2.status_code == 200
    assert resp2.json()["data"]["signup_completed_at"] == first_ts


# ── T-12: record-signup-completed on closed → 200 no-op ──────────────────────


@pytest.mark.asyncio
async def test_t12_record_signup_completed_on_closed_noop(client: AsyncClient, db_session: AsyncSession):
    """T-12: POST record-signup-completed on closed enquiry → 200 'no matching open enquiry'."""
    enq = await _create_enquiry(db_session, status="closed")

    resp = await client.post(
        "/api/site-admin/enquiries/record-signup-completed",
        json={"enquiry_id": str(enq.id)},
    )
    assert resp.status_code == 200
    assert "no matching open enquiry" in resp.json()["message"].lower()
    # Status still closed, signup_completed_at still null
    assert resp.json()["data"] == {}


# ── T-13: auto_close_stale_qualified — 31 days, no signup → closed ───────────


@pytest.mark.asyncio
async def test_t13_auto_close_stale_qualified(db_session: AsyncSession):
    """T-13: auto_close_stale_qualified() closes enquiry with qualified_email_sent_at 31 days ago
    and signup_completed_at IS NULL."""
    stale_ts = datetime.now(timezone.utc) - timedelta(days=31)
    enq = await _create_enquiry(db_session, status="qualified", qualified_email_sent_at=stale_ts)

    service = EnquiryService(db_session)
    closed_ids = await service.auto_close_stale_qualified()

    assert enq.id in closed_ids
    await db_session.refresh(enq)
    assert enq.status == "closed"
    assert enq.auto_closed_at is not None


# ── T-14: auto_close — 29 days → unchanged ───────────────────────────────────


@pytest.mark.asyncio
async def test_t14_auto_close_not_yet_stale(db_session: AsyncSession):
    """T-14: auto_close_stale_qualified() does NOT close enquiry with qualified_email_sent_at 29 days ago."""
    recent_ts = datetime.now(timezone.utc) - timedelta(days=29)
    enq = await _create_enquiry(db_session, status="qualified", qualified_email_sent_at=recent_ts)

    service = EnquiryService(db_session)
    closed_ids = await service.auto_close_stale_qualified()

    assert enq.id not in closed_ids
    await db_session.refresh(enq)
    assert enq.status == "qualified"


# ── T-15: auto_close skips enquiry with signup_completed_at set ──────────────


@pytest.mark.asyncio
async def test_t15_auto_close_skips_converted(db_session: AsyncSession):
    """T-15: auto_close skips qualified enquiry where signup_completed_at IS NOT NULL."""
    stale_ts = datetime.now(timezone.utc) - timedelta(days=35)
    signup_ts = datetime.now(timezone.utc) - timedelta(days=5)
    enq = await _create_enquiry(
        db_session,
        status="qualified",
        qualified_email_sent_at=stale_ts,
        signup_completed_at=signup_ts,
    )

    service = EnquiryService(db_session)
    closed_ids = await service.auto_close_stale_qualified()

    assert enq.id not in closed_ids
    await db_session.refresh(enq)
    assert enq.status == "qualified"


# ── T-16: audit log written on each transition ───────────────────────────────


@pytest.mark.asyncio
async def test_t16_audit_log_written_on_transition(client: AsyncClient, db_session: AsyncSession):
    """T-16: Audit log contains correct from_status, to_status, trigger_source, actor_email."""
    headers, actor = await _make_sa_headers(db_session)
    enq = await _create_enquiry(db_session, status="new")

    resp = await client.post(
        f"/api/site-admin/enquiries/{enq.id}/mark-contacted",
        headers=headers,
    )
    assert resp.status_code == 200

    result = await db_session.execute(
        select(EnquiryAuditLog).where(EnquiryAuditLog.enquiry_id == enq.id)
    )
    logs = result.scalars().all()
    assert len(logs) == 1
    log = logs[0]
    assert log.from_status == "new"
    assert log.to_status == "contacted"
    assert log.trigger_source == "MANUAL"
    assert log.actor_email == actor.email


# ── T-17: GET /enquiries/{id} returns audit_logs array ───────────────────────


@pytest.mark.asyncio
async def test_t17_get_enquiry_detail_includes_audit_logs(client: AsyncClient, db_session: AsyncSession):
    """T-17: GET /enquiries/{id} returns enquiry detail with audit_logs list."""
    headers, _ = await _make_sa_headers(db_session)
    enq = await _create_enquiry(db_session, status="new")

    # Transition to contacted to create an audit log
    await client.post(
        f"/api/site-admin/enquiries/{enq.id}/mark-contacted",
        headers=headers,
    )

    resp = await client.get(f"/api/site-admin/enquiries/{enq.id}", headers=headers)
    assert resp.status_code == 200
    data = resp.json()["data"]
    assert "audit_logs" in data
    assert len(data["audit_logs"]) == 1
    assert data["audit_logs"][0]["from_status"] == "new"
    assert data["audit_logs"][0]["to_status"] == "contacted"


# ── T-18: new-count counts only status='new' ─────────────────────────────────


@pytest.mark.asyncio
async def test_t18_new_count_only_counts_new_status(client: AsyncClient, db_session: AsyncSession):
    """T-18: GET /enquiries/new-count returns only count of status='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")
    await _create_enquiry(db_session, status="qualified")
    await _create_enquiry(db_session, status="closed")

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


# ── T-19: mark-contacted on closed → 422 terminal state ─────────────────────


@pytest.mark.asyncio
async def test_t19_mark_contacted_on_closed_422(client: AsyncClient, db_session: AsyncSession):
    """T-19: POST mark-contacted on 'closed' enquiry → HTTP 422 (terminal state)."""
    headers, _ = await _make_sa_headers(db_session)
    enq = await _create_enquiry(db_session, status="closed")

    resp = await client.post(
        f"/api/site-admin/enquiries/{enq.id}/mark-contacted",
        headers=headers,
    )
    assert resp.status_code == 422


# ── T-20: SIGNUP_COMPLETE audit log has same from/to status ──────────────────


@pytest.mark.asyncio
async def test_t20_signup_complete_audit_log_same_status(client: AsyncClient, db_session: AsyncSession):
    """T-20: SIGNUP_COMPLETE audit log has from_status='qualified' and to_status='qualified'."""
    enq = await _create_enquiry(db_session, status="qualified")

    resp = await client.post(
        "/api/site-admin/enquiries/record-signup-completed",
        json={"enquiry_id": str(enq.id)},
    )
    assert resp.status_code == 200

    result = await db_session.execute(
        select(EnquiryAuditLog).where(EnquiryAuditLog.enquiry_id == enq.id)
    )
    logs = result.scalars().all()
    assert len(logs) == 1
    log = logs[0]
    assert log.from_status == "qualified"
    assert log.to_status == "qualified"
    assert log.trigger_source == "SIGNUP_COMPLETE"
