"""
Public-facing router — no authentication required.
All endpoints are scoped to the tenant resolved from the subdomain via middleware.
"""
from datetime import datetime, timezone
from typing import Optional

from fastapi import APIRouter, Depends, Query, Request
from sqlalchemy import and_, func, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from src.apps.memorials.models.memorial import Memorial
from src.apps.memorials.models.tribute import Tribute
from src.apps.memorials.schemas.requests import CreateTributeRequest
from src.apps.memorials.schemas.responses import TributeResponse
from src.apps.news.models.news_post import NewsPost
from src.apps.news.schemas.responses import NewsPostResponse
from src.apps.plots.models.plot import Plot

from src.apps.records.models.record import Record
from src.apps.records.schemas.responses import RecordSummaryResponse
from src.apps.public.models.website_enquiry import WebsiteEnquiry
from src.apps.public.schemas.contact import BookACallRequest, ContactEnquiryRequest
from src.apps.tenants.models.account import Account
from src.apps.tenants.schemas.requests import PublicSignupRequest
from src.apps.tenants.services.tenant_service import TenantService
from src.core.exceptions import NotFoundError
from src.core.schemas.response import paginated, success
from src.database.session import get_db

router = APIRouter(prefix="/public", tags=["Public"])


@router.get("/tenants/by-subdomain/{subdomain}", response_model=dict)
async def public_get_tenant(
    subdomain: str,
    db: AsyncSession = Depends(get_db),
):
    """Tenant config lookup used by the admin portal TenantProvider."""
    result = await db.execute(
        select(Account)
        .options(selectinload(Account.branding))
        .where(Account.subdomain == subdomain)
    )
    account = result.scalar_one_or_none()
    if not account:
        raise NotFoundError("Cemetery not found")

    b = account.branding
    plan = account.plan
    cfg = account.config_json or {}

    return success(data={
        "tenantId": str(account.id),
        "subdomain": account.subdomain,
        "tenantName": account.organization_name,
        "status": account.status,
        "timezone": cfg.get("timezone", "America/Toronto"),
        "locale": cfg.get("locale", "en-CA"),
        "branding": {
            "primaryColor": "#1B4332",
            "accentColor": (b.accent_color if b else None) or "#52B788",
            "logoUrl": b.logo_url if b else None,
            "faviconUrl": b.favicon_url if b else None,
            "publicSiteName": (b.public_site_name if b else None) or account.organization_name,
            "locationTagline": (b.location_tagline if b else None) or "",
            "customCss": b.custom_css if b else None,
        },
        "features": {
            "planType": plan,
            "aiSearch": plan in ("professional", "enterprise"),
            "aiRecordExtraction": plan == "enterprise",
            "aiBiographyWriter": plan in ("professional", "enterprise"),
            "onlineInquiries": True,
            "tributeSubmissions": True,
            "publicMap": True,
        },
    })


@router.get("/records", response_model=dict)
async def public_search_records(
    name: Optional[str] = Query(None),
    section: Optional[str] = Query(None),
    skip: int = Query(0, ge=0),
    limit: int = Query(20, ge=1, le=100),
    request: Request = None,
    db: AsyncSession = Depends(get_db),
):
    """Public search for burial records. Only returns records with visibility_config='public'."""
    tenant_id = getattr(request.state, "tenant_id", None) if request else None
    if not tenant_id:
        return paginated(items=[], total=0, page=1, page_size=limit)

    conditions = [
        Record.tenant_id == tenant_id,
        Record.deleted_at.is_(None),
        Record.visibility_config == "public",
    ]

    if name:
        term = f"%{name.lower()}%"
        from sqlalchemy import or_
        conditions.append(
            or_(
                func.lower(Record.first_name).like(term),
                func.lower(Record.last_name).like(term),
                func.lower(func.concat(Record.first_name, " ", Record.last_name)).like(term),
            )
        )

    where_clause = and_(*conditions)

    count_result = await db.execute(select(func.count(Record.id)).where(where_clause))
    total = count_result.scalar_one()

    result = await db.execute(
        select(Record)
        .where(where_clause)
        .order_by(Record.last_name.asc(), Record.first_name.asc())
        .offset(skip)
        .limit(limit)
    )
    records = result.scalars().all()
    return paginated(
        items=[RecordSummaryResponse.model_validate(r).model_dump() for r in records],
        total=total,
        page=(skip // limit) + 1,
        page_size=limit,
    )


@router.get("/memorials/{slug}", response_model=dict)
async def public_get_memorial(
    slug: str,
    request: Request,
    db: AsyncSession = Depends(get_db),
):
    """Get a published memorial by slug for the public memorial page."""
    tenant_id = getattr(request.state, "tenant_id", None)
    if not tenant_id:
        raise NotFoundError("Memorial not found")

    result = await db.execute(
        select(Memorial)
        .options(selectinload(Memorial.tributes))
        .where(
            and_(
                Memorial.tenant_id == tenant_id,
                Memorial.slug == slug,
                Memorial.is_published.is_(True),
            )
        )
    )
    memorial = result.scalar_one_or_none()
    if not memorial:
        raise NotFoundError("Memorial not found")

    # Only include approved tributes for public view
    from src.apps.memorials.schemas.responses import MemorialResponse
    data = MemorialResponse.model_validate(memorial).model_dump()
    data["tributes"] = [
        TributeResponse.model_validate(t).model_dump()
        for t in memorial.tributes
        if t.status == "approved"
    ]
    return success(data=data)


@router.post("/tributes", response_model=dict, status_code=201)
async def public_submit_tribute(
    body: CreateTributeRequest,
    request: Request,
    memorial_id: str = Query(..., description="UUID of the memorial to submit tribute for"),
    db: AsyncSession = Depends(get_db),
):
    """Public tribute submission. Status defaults to 'pending' for moderation."""
    from uuid import UUID
    tenant_id = getattr(request.state, "tenant_id", None)
    if not tenant_id:
        raise NotFoundError("Memorial not found")

    memorial_uuid = UUID(memorial_id)

    # Verify memorial exists and is published
    result = await db.execute(
        select(Memorial).where(
            and_(
                Memorial.id == memorial_uuid,
                Memorial.tenant_id == tenant_id,
                Memorial.is_published.is_(True),
            )
        )
    )
    memorial = result.scalar_one_or_none()
    if not memorial:
        raise NotFoundError("Memorial not found")

    tribute = Tribute(
        tenant_id=tenant_id,
        memorial_id=memorial_uuid,
        status="pending",
        submitted_at=datetime.now(timezone.utc),
        **body.model_dump(exclude_none=True),
    )
    db.add(tribute)
    await db.flush()
    await db.refresh(tribute)
    return success(
        data=TributeResponse.model_validate(tribute).model_dump(),
        message="Tribute submitted and pending moderation",
    )


@router.get("/news", response_model=dict)
async def public_list_news(
    skip: int = Query(0, ge=0),
    limit: int = Query(10, ge=1, le=50),
    request: Request = None,
    db: AsyncSession = Depends(get_db),
):
    """List published news posts for the public site."""
    tenant_id = getattr(request.state, "tenant_id", None) if request else None
    if not tenant_id:
        return paginated(items=[], total=0, page=1, page_size=limit)

    conditions = [
        NewsPost.tenant_id == tenant_id,
        NewsPost.status == "published",
    ]
    where_clause = and_(*conditions)

    count_result = await db.execute(select(func.count(NewsPost.id)).where(where_clause))
    total = count_result.scalar_one()

    result = await db.execute(
        select(NewsPost)
        .where(where_clause)
        .order_by(NewsPost.published_at.desc())
        .offset(skip)
        .limit(limit)
    )
    posts = result.scalars().all()
    return paginated(
        items=[NewsPostResponse.model_validate(p).model_dump() for p in posts],
        total=total,
        page=(skip // limit) + 1,
        page_size=limit,
    )


@router.get("/plots/available", response_model=dict)
async def public_available_plots(
    skip: int = Query(0, ge=0),
    limit: int = Query(20, ge=1, le=100),
    request: Request = None,
    db: AsyncSession = Depends(get_db),
):
    """List available plots with pricing for the public cemetery map/shop."""
    tenant_id = getattr(request.state, "tenant_id", None) if request else None
    if not tenant_id:
        return paginated(items=[], total=0, page=1, page_size=limit)

    conditions = [
        Plot.tenant_id == tenant_id,
        Plot.status == "vacant",
    ]
    where_clause = and_(*conditions)

    count_result = await db.execute(select(func.count(Plot.id)).where(where_clause))
    total = count_result.scalar_one()

    result = await db.execute(
        select(Plot)
        .options(selectinload(Plot.plot_type), selectinload(Plot.section))
        .where(where_clause)
        .order_by(Plot.created_at.asc())
        .offset(skip)
        .limit(limit)
    )
    plots = result.scalars().all()

    def _serialize_plot(p: Plot) -> dict:
        price = p.price_override
        if price is None and p.plot_type:
            price = p.plot_type.default_price
        return {
            "id": str(p.id),
            "plot_ref": p.plot_ref,
            "status": p.status,
            "section_name": p.section.name if p.section else None,
            "plot_type_name": p.plot_type.name if p.plot_type else None,
            "price": float(price) if price is not None else None,
            "latitude": float(p.latitude) if p.latitude else None,
            "longitude": float(p.longitude) if p.longitude else None,
            "is_veteran_section": p.is_veteran_section,
        }

    return paginated(
        items=[_serialize_plot(p) for p in plots],
        total=total,
        page=(skip // limit) + 1,
        page_size=limit,
    )


@router.post("/contact", response_model=dict, status_code=201)
async def public_contact(
    body: ContactEnquiryRequest,
    request: Request,
    db: AsyncSession = Depends(get_db),
):
    """
    Submit a contact/enquiry form.
    Persists to website_enquiries table.
    Honeypot field (website) silently discards bot submissions.
    """
    # Honeypot: if filled, appear to succeed but don't persist
    if body.website:
        return success(
            data={"id": None},
            message="Your enquiry has been received. We will be in touch within one business day.",
        )

    ip_address = request.client.host if request.client else None
    user_agent = request.headers.get("user-agent")

    enquiry = WebsiteEnquiry(
        name=body.name,
        email=body.email,
        organization=body.organization,
        cemetery_type=body.cemetery_type,
        message=body.message,
        source=body.source,
        ip_address=ip_address,
        user_agent=user_agent,
        status="new",
        type=body.type,
        role=body.role,
        phone=body.phone,
    )
    db.add(enquiry)
    await db.flush()
    await db.refresh(enquiry)

    return success(
        data={"id": str(enquiry.id)},
        message="Your enquiry has been received. We will be in touch within one business day.",
    )


@router.post("/book-a-call", response_model=dict, status_code=201)
async def public_book_a_call(
    body: BookACallRequest,
    request: Request,
    db: AsyncSession = Depends(get_db),
):
    """
    Submit a book-a-call request from the marketing site.
    Stores type='call' with day/time preferences.
    Honeypot field (website) silently discards bot submissions.
    """
    if body.website:
        return success(
            data={"id": None},
            message="Your call booking has been received. We will be in touch within one business day.",
        )

    ip_address = request.client.host if request.client else None
    user_agent = request.headers.get("user-agent")

    enquiry = WebsiteEnquiry(
        name=body.name,
        email=body.email,
        organization=body.organization,
        cemetery_type=body.cemetery_type,
        message=body.message,
        source="marketing",
        ip_address=ip_address,
        user_agent=user_agent,
        status="new",
        type="call",
        role=body.role,
        phone=body.phone,
        day=body.day,
        time=body.time,
    )
    db.add(enquiry)
    await db.flush()
    await db.refresh(enquiry)

    return success(
        data={"id": str(enquiry.id)},
        message="Your call booking has been received. We will be in touch within one business day.",
    )


@router.get("/check-email", response_model=dict)
async def check_email_exists(
    email: str = Query(..., description="Email address to check"),
    db: AsyncSession = Depends(get_db),
):
    """
    Lightweight email existence check for the signup form.
    Called on blur — returns { exists: bool } without creating any records.
    Always returns HTTP 200 so the frontend can handle the result inline.
    """
    from sqlalchemy import func
    from src.apps.auth.models.user import User

    result = await db.execute(
        select(User).where(
            func.lower(User.email) == email.lower().strip(),
            User.deleted_at.is_(None),
        )
    )
    exists = result.scalar_one_or_none() is not None
    return success(data={"exists": exists})


@router.post("/signup", response_model=dict, status_code=201)
async def public_signup(
    body: PublicSignupRequest,
    request: Request,
    db: AsyncSession = Depends(get_db),
):
    """
    Public self-service signup — creates a new tenant (cemetery account) and admin user.

    Auto-derives subdomain from organization_name, generates a secure temporary
    password, and dispatches a welcome email with workspace URL and credentials.
    """
    import logging as _logging
    from src.core.utils.url import build_workspace_url
    from src.core.utils.text import split_full_name
    from src.core.email import send_welcome_email

    _logger = _logging.getLogger(__name__)

    # Extract client IP for audit log
    forwarded_for = request.headers.get("x-forwarded-for")
    ip_address = (
        forwarded_for.split(",")[0].strip()
        if forwarded_for
        else (request.client.host if request.client else None)
    )

    service = TenantService(db)
    account, admin_user, temp_password = await service.create_from_public_signup(
        body.model_dump(), ip_address=ip_address
    )

    # Build workspace URL and send welcome email
    # Email failure must never fail the signup — log and continue
    workspace_url = build_workspace_url(account.subdomain)
    first_name, _ = split_full_name(body.name)
    try:
        await send_welcome_email(
            to=admin_user.email,
            first_name=first_name,
            organization_name=account.organization_name,
            workspace_url=workspace_url,
            temp_password=temp_password,
        )
    except Exception as exc:  # noqa: BLE001
        _logger.error("Welcome email failed for %s: %s", admin_user.email, exc)

    return success(
        data={
            "account_id": str(account.id),
            "subdomain": account.subdomain,
            "organization_name": account.organization_name,
            "status": account.status,
            "admin_email": admin_user.email,
        },
        message="Cemetery account created. Check your email for your workspace URL and login credentials.",
    )
