"""
Tests for INDL-03 — Site Admin Sign-ups Module.
Covers: GET /signups, GET /signups/kpis, POST /tenants (admin create),
        POST /tenants/{id}/send-credentials, PATCH /tenants/{id} (extended).
Test matrix rows: T-01 through T-24 (backend-equivalent coverage).
"""
import pytest
from httpx import AsyncClient
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from src.apps.tenants.models.account import Account
from src.apps.auth.models.user import User
from src.core.security import hash_password


# ── Fixtures ─────────────────────────────────────────────────────────────────


@pytest.fixture
def site_admin_headers(site_admin_token: str) -> dict:
    return {"Authorization": f"Bearer {site_admin_token}"}


@pytest.fixture
def admin_signup_payload() -> dict:
    return {
        "organization_name": "Maplewood Cemetery",
        "cemetery_type": "Municipal cemetery",
        "contact_name": "Jordan Lee",
        "contact_email": "jordan@maplewood-test.ca",
        "contact_phone": "(613) 555-0100",
        "size": "Under 5,000 plots",
        "plan": "professional",
        "signup_source": "admin",
    }


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


async def _create_site_admin(db_session: AsyncSession) -> tuple[User, str]:
    """Create a site_admin user and return (user, token)."""
    from src.core.security import create_access_token, build_token_payload

    user = User(
        tenant_id=None,
        email=f"site.admin.{id(db_session)}@indelis.com",
        password_hash=hash_password("SiteAdmin123!"),
        first_name="Site",
        last_name="Admin",
        role="site_admin",
        status="active",
    )
    db_session.add(user)
    await db_session.flush()

    payload = build_token_payload(user)
    token = create_access_token(payload)
    return user, token


async def _create_test_account(
    db_session: AsyncSession,
    *,
    org: str = "Test Cemetery",
    subdomain: str = "testcemetery-sa",
    status: str = "pending",
    plan: str = "starter",
    contact_name: str = "Test Admin",
) -> Account:
    account = Account(
        organization_name=org,
        subdomain=subdomain,
        contact_email=f"admin@{subdomain}.ca",
        plan=plan,
        status=status,
        signup_source="marketing",
    )
    db_session.add(account)
    await db_session.flush()

    first, *rest = contact_name.split(" ", 1)
    last = rest[0] if rest else ""
    admin = User(
        tenant_id=account.id,
        email=f"admin@{subdomain}.ca",
        password_hash=hash_password("Password123"),
        first_name=first,
        last_name=last,
        role="administrator",
        status="active",
    )
    db_session.add(admin)
    await db_session.flush()
    return account


# ── KPI endpoint ─────────────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_kpis_returns_counts(client: AsyncClient, db_session: AsyncSession):
    """GET /api/site-admin/signups/kpis — returns KPI dict with expected keys."""
    _, token = await _create_site_admin(db_session)
    headers = {"Authorization": f"Bearer {token}"}

    # Create accounts in different statuses
    await _create_test_account(db_session, subdomain="kpi-new1", status="pending")
    await _create_test_account(db_session, subdomain="kpi-onboarding1", status="onboarding")
    await _create_test_account(db_session, subdomain="kpi-active1", status="active")

    resp = await client.get("/api/site-admin/signups/kpis", headers=headers)
    assert resp.status_code == 200
    body = resp.json()
    assert body["success"] is True
    kpis = body["data"]
    assert "new" in kpis
    assert "onboarding" in kpis
    assert "active" in kpis
    assert "total" in kpis
    assert kpis["total"] >= 3
    assert kpis["onboarding"] >= 1
    assert kpis["active"] >= 1


@pytest.mark.asyncio
async def test_kpis_requires_site_admin(client: AsyncClient, auth_headers: dict):
    """KPI endpoint returns 403 for non-site_admin users."""
    resp = await client.get("/api/site-admin/signups/kpis", headers=auth_headers)
    assert resp.status_code == 403


# ── List signups ──────────────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_list_signups_returns_paginated(client: AsyncClient, db_session: AsyncSession):
    """GET /api/site-admin/signups — returns paginated data with items."""
    _, token = await _create_site_admin(db_session)
    headers = {"Authorization": f"Bearer {token}"}

    await _create_test_account(db_session, subdomain="list-test1", org="Oakwood Memorial")

    resp = await client.get("/api/site-admin/signups", headers=headers)
    assert resp.status_code == 200
    body = resp.json()
    assert body["success"] is True
    assert body["total"] >= 1
    items = body["data"]
    assert isinstance(items, list)

    item = next((i for i in items if "Oakwood" in i.get("name", "")), None)
    assert item is not None
    assert "contact" in item
    assert "plan" in item
    assert "status" in item
    assert "created_at" in item


@pytest.mark.asyncio
async def test_list_signups_filter_by_status(client: AsyncClient, db_session: AsyncSession):
    """GET /api/site-admin/signups?status=onboarding — filters by status."""
    _, token = await _create_site_admin(db_session)
    headers = {"Authorization": f"Bearer {token}"}

    await _create_test_account(db_session, subdomain="filter-onboard1", status="onboarding")

    resp = await client.get("/api/site-admin/signups?status=onboarding", headers=headers)
    assert resp.status_code == 200
    items = resp.json()["data"]
    for item in items:
        assert item["status"] == "onboarding"


@pytest.mark.asyncio
async def test_list_signups_filter_by_plan(client: AsyncClient, db_session: AsyncSession):
    """GET /api/site-admin/signups?plan=enterprise — filters by plan."""
    _, token = await _create_site_admin(db_session)
    headers = {"Authorization": f"Bearer {token}"}

    await _create_test_account(
        db_session, subdomain="filter-enterprise1", plan="enterprise", status="active"
    )

    resp = await client.get("/api/site-admin/signups?plan=enterprise", headers=headers)
    assert resp.status_code == 200
    items = resp.json()["data"]
    for item in items:
        assert item["plan"] == "enterprise"


@pytest.mark.asyncio
async def test_list_signups_search_by_org(client: AsyncClient, db_session: AsyncSession):
    """GET /api/site-admin/signups?q=Riverside — filters by org name."""
    _, token = await _create_site_admin(db_session)
    headers = {"Authorization": f"Bearer {token}"}

    await _create_test_account(
        db_session, subdomain="riverside-search1", org="Riverside Memorial"
    )

    resp = await client.get("/api/site-admin/signups?q=Riverside", headers=headers)
    assert resp.status_code == 200
    items = resp.json()["data"]
    assert any("Riverside" in i["name"] for i in items)


@pytest.mark.asyncio
async def test_list_signups_requires_site_admin(client: AsyncClient, auth_headers: dict):
    """List signups endpoint returns 403 for non-site_admin."""
    resp = await client.get("/api/site-admin/signups", headers=auth_headers)
    assert resp.status_code == 403


@pytest.mark.asyncio
async def test_list_signups_contact_included(client: AsyncClient, db_session: AsyncSession):
    """Sign-up list items include derived contact name from administrator user."""
    _, token = await _create_site_admin(db_session)
    headers = {"Authorization": f"Bearer {token}"}

    await _create_test_account(
        db_session,
        subdomain="contact-test1",
        org="Contact Test Cemetery",
        contact_name="Alice Dupont",
    )

    resp = await client.get("/api/site-admin/signups?q=Contact+Test", headers=headers)
    items = resp.json()["data"]
    assert len(items) >= 1
    contact = items[0]["contact"]
    assert "Alice" in contact.get("name", "")


# ── Admin create tenant ────────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_admin_create_tenant_success(
    client: AsyncClient, db_session: AsyncSession, admin_signup_payload: dict
):
    """POST /api/site-admin/tenants — creates account + admin user + temp_password."""
    _, token = await _create_site_admin(db_session)
    headers = {"Authorization": f"Bearer {token}"}

    resp = await client.post("/api/site-admin/tenants", json=admin_signup_payload, headers=headers)
    assert resp.status_code == 201
    body = resp.json()
    assert body["success"] is True
    data = body["data"]
    assert "account" in data
    assert "admin_user" in data
    assert "temp_password" in data
    assert len(data["temp_password"]) >= 10

    # Verify DB
    result = await db_session.execute(
        select(Account).where(Account.contact_email == "jordan@maplewood-test.ca")
    )
    account = result.scalar_one_or_none()
    assert account is not None
    assert account.signup_source == "admin"
    assert account.size == "Under 5,000 plots"


@pytest.mark.asyncio
async def test_admin_create_tenant_duplicate_email(
    client: AsyncClient, db_session: AsyncSession, admin_signup_payload: dict
):
    """POST /api/site-admin/tenants — duplicate email returns 409."""
    _, token = await _create_site_admin(db_session)
    headers = {"Authorization": f"Bearer {token}"}

    await client.post("/api/site-admin/tenants", json=admin_signup_payload, headers=headers)
    # Second attempt with same email
    resp = await client.post("/api/site-admin/tenants", json=admin_signup_payload, headers=headers)
    assert resp.status_code == 409


@pytest.mark.asyncio
async def test_admin_create_tenant_invalid_plan(
    client: AsyncClient, db_session: AsyncSession, admin_signup_payload: dict
):
    """POST /api/site-admin/tenants — invalid plan returns 422."""
    _, token = await _create_site_admin(db_session)
    headers = {"Authorization": f"Bearer {token}"}

    payload = {**admin_signup_payload, "contact_email": "badplan@test.ca", "plan": "diamond"}
    resp = await client.post("/api/site-admin/tenants", json=payload, headers=headers)
    assert resp.status_code == 422


@pytest.mark.asyncio
async def test_admin_create_tenant_requires_site_admin(
    client: AsyncClient, auth_headers: dict, admin_signup_payload: dict
):
    """POST /api/site-admin/tenants — non-site_admin gets 403."""
    resp = await client.post("/api/site-admin/tenants", json=admin_signup_payload, headers=auth_headers)
    assert resp.status_code == 403


# ── Send credentials ───────────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_send_credentials_records_timestamp(client: AsyncClient, db_session: AsyncSession):
    """POST /api/site-admin/tenants/{id}/send-credentials — records credentials_sent_at."""
    _, token = await _create_site_admin(db_session)
    headers = {"Authorization": f"Bearer {token}"}

    account = await _create_test_account(db_session, subdomain="creds-test1")
    sent_at = "2026-06-24T10:30:00.000Z"

    resp = await client.post(
        f"/api/site-admin/tenants/{account.id}/send-credentials",
        json={"credentials_sent_at": sent_at},
        headers=headers,
    )
    assert resp.status_code == 200
    body = resp.json()
    assert body["success"] is True
    assert body["data"]["credentials_sent_at"] is not None

    # Verify DB
    await db_session.refresh(account)
    assert account.credentials_sent_at is not None


@pytest.mark.asyncio
async def test_send_credentials_not_found(client: AsyncClient, db_session: AsyncSession):
    """POST send-credentials for unknown tenant → 404."""
    import uuid
    _, token = await _create_site_admin(db_session)
    headers = {"Authorization": f"Bearer {token}"}

    resp = await client.post(
        f"/api/site-admin/tenants/{uuid.uuid4()}/send-credentials",
        json={"credentials_sent_at": "2026-06-24T10:00:00Z"},
        headers=headers,
    )
    assert resp.status_code == 404


@pytest.mark.asyncio
async def test_send_credentials_requires_site_admin(
    client: AsyncClient, auth_headers: dict, test_account
):
    """Non-site_admin cannot send credentials."""
    resp = await client.post(
        f"/api/site-admin/tenants/{test_account.id}/send-credentials",
        json={"credentials_sent_at": "2026-06-24T10:00:00Z"},
        headers=auth_headers,
    )
    assert resp.status_code == 403


# ── PATCH tenant (extended fields) ────────────────────────────────────────────


@pytest.mark.asyncio
async def test_patch_tenant_status_sets_activated_at(
    client: AsyncClient, db_session: AsyncSession
):
    """PATCH status → active auto-sets activated_at on first transition."""
    _, token = await _create_site_admin(db_session)
    headers = {"Authorization": f"Bearer {token}"}

    account = await _create_test_account(db_session, subdomain="activate-test1", status="onboarding")
    assert account.activated_at is None

    resp = await client.patch(
        f"/api/site-admin/tenants/{account.id}",
        json={"status": "active"},
        headers=headers,
    )
    assert resp.status_code == 200
    await db_session.refresh(account)
    assert account.status == "active"
    assert account.activated_at is not None


@pytest.mark.asyncio
async def test_patch_tenant_feature_flags(client: AsyncClient, db_session: AsyncSession):
    """PATCH feature_flags merges flag overrides into account."""
    _, token = await _create_site_admin(db_session)
    headers = {"Authorization": f"Bearer {token}"}

    account = await _create_test_account(db_session, subdomain="flags-test1")
    flags = {
        "aiSearch": False,
        "aiRecordExtraction": False,
        "aiBiographyWriter": True,
        "onlineInquiries": True,
        "tributeSubmissions": True,
        "publicMap": False,
    }

    resp = await client.patch(
        f"/api/site-admin/tenants/{account.id}",
        json={"feature_flags": flags},
        headers=headers,
    )
    assert resp.status_code == 200
    await db_session.refresh(account)
    assert account.feature_flags is not None
    assert account.feature_flags.get("aiBiographyWriter") is True


@pytest.mark.asyncio
async def test_patch_tenant_account_manager(client: AsyncClient, db_session: AsyncSession):
    """PATCH account_manager saves the value."""
    _, token = await _create_site_admin(db_session)
    headers = {"Authorization": f"Bearer {token}"}

    account = await _create_test_account(db_session, subdomain="manager-test1")

    resp = await client.patch(
        f"/api/site-admin/tenants/{account.id}",
        json={"account_manager": "Sarah Chen"},
        headers=headers,
    )
    assert resp.status_code == 200
    await db_session.refresh(account)
    assert account.account_manager == "Sarah Chen"


@pytest.mark.asyncio
async def test_patch_tenant_size(client: AsyncClient, db_session: AsyncSession):
    """PATCH size saves the size band."""
    _, token = await _create_site_admin(db_session)
    headers = {"Authorization": f"Bearer {token}"}

    account = await _create_test_account(db_session, subdomain="size-test1")

    resp = await client.patch(
        f"/api/site-admin/tenants/{account.id}",
        json={"size": "5,000 – 20,000 plots"},
        headers=headers,
    )
    assert resp.status_code == 200
    await db_session.refresh(account)
    assert account.size == "5,000 – 20,000 plots"


@pytest.mark.asyncio
async def test_patch_tenant_requires_site_admin(
    client: AsyncClient, auth_headers: dict, test_account
):
    """Non-site_admin cannot patch tenant."""
    resp = await client.patch(
        f"/api/site-admin/tenants/{test_account.id}",
        json={"status": "active"},
        headers=auth_headers,
    )
    assert resp.status_code == 403


# ── TenantService unit tests ───────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_get_signup_kpis_counts(db_session: AsyncSession):
    """TenantService.get_signup_kpis() returns correct counts."""
    from src.apps.tenants.services.tenant_service import TenantService

    service = TenantService(db_session)

    # Create accounts in various statuses
    for i, status in enumerate(["pending", "onboarding", "active", "active", "suspended"]):
        account = Account(
            organization_name=f"KPI Acct {i}",
            subdomain=f"kpi-svc-{i}-{status}",
            contact_email=f"kpi{i}@test.ca",
            plan="starter",
            status=status,
        )
        db_session.add(account)
    await db_session.flush()

    kpis = await service.get_signup_kpis()
    assert kpis["onboarding"] >= 1
    assert kpis["active"] >= 2
    assert kpis["total"] >= 5


@pytest.mark.asyncio
async def test_set_feature_flags_merges(db_session: AsyncSession):
    """TenantService.set_feature_flags() merges into existing flags."""
    from src.apps.tenants.services.tenant_service import TenantService

    account = Account(
        organization_name="Flag Test",
        subdomain="flag-merge-test",
        contact_email="flags@test.ca",
        plan="starter",
        status="active",
        feature_flags={"aiSearch": True},
    )
    db_session.add(account)
    await db_session.flush()

    service = TenantService(db_session)
    result = await service.set_feature_flags(account.id, {"onlineInquiries": True})
    assert result.feature_flags.get("aiSearch") is True
    assert result.feature_flags.get("onlineInquiries") is True


@pytest.mark.asyncio
async def test_send_credentials_service(db_session: AsyncSession):
    """TenantService.send_credentials() sets credentials_sent_at."""
    from src.apps.tenants.services.tenant_service import TenantService

    account = Account(
        organization_name="Creds Svc Test",
        subdomain="creds-svc-test",
        contact_email="creds-svc@test.ca",
        plan="starter",
        status="active",
    )
    db_session.add(account)
    await db_session.flush()
    assert account.credentials_sent_at is None

    service = TenantService(db_session)
    updated = await service.send_credentials(account.id, "2026-06-24T10:00:00Z")
    assert updated.credentials_sent_at is not None


@pytest.mark.asyncio
async def test_admin_create_service(db_session: AsyncSession):
    """TenantService.admin_create() returns account + user + temp_password."""
    from src.apps.tenants.services.tenant_service import TenantService

    service = TenantService(db_session)
    account, user, temp_pw = await service.admin_create({
        "organization_name": "Admin Create Test",
        "contact_email": "admin-create-svc@test.ca",
        "contact_name": "Test User",
        "plan": "starter",
        "signup_source": "admin",
    })
    assert account.signup_source == "admin"
    assert user.role == "administrator"
    assert len(temp_pw) >= 10
