"""Dashboard auth middleware — require_dashboard_auth() dependency.

Validates session cookie, resolves customer context, handles
post-cancellation grace period and htmx redirect patterns.
"""

from __future__ import annotations

import hashlib
import logging
from dataclasses import dataclass
from datetime import UTC, datetime, timedelta
from typing import TYPE_CHECKING, Any

from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse

if TYPE_CHECKING:
    from fastapi import Request

_log = logging.getLogger(__name__)

_LOGIN_URL = "/auth/login"
_GRACE_PERIOD_DAYS = 30


@dataclass
class CustomerContext:
    """Resolved dashboard customer context from valid session."""

    customer_id: str
    email: str
    tier: str
    role: str
    member_email: str
    features: list[str]
    project_limit: int | None
    batch_limit: int | None
    cancellation_pending: bool
    subscription_expires_at: str
    consent_accepted: bool
    tos_version: str
    privacy_version: str


class DashboardAuthError(Exception):
    """Raised when dashboard auth fails."""

    def __init__(
        self,
        *,
        redirect: bool = True,
        htmx: bool = False,
        message: str = "",
    ) -> None:
        self.redirect = redirect
        self.htmx = htmx
        self.message = message
        super().__init__(message)


def _is_htmx(request: Request) -> bool:
    return bool(request.headers.get("HX-Request"))


def _build_redirect(
    request: Request,
) -> RedirectResponse | HTMLResponse:
    """Build login redirect — htmx-aware."""
    if _is_htmx(request):
        return HTMLResponse(
            status_code=200,
            headers={"HX-Redirect": _LOGIN_URL},
        )
    return RedirectResponse(url=_LOGIN_URL, status_code=302)


async def require_dashboard_auth(
    request: Request,
) -> CustomerContext:
    """Dashboard auth middleware for all /dashboard/ routes.

    Decrypts session cookie, verifies customer tier, handles
    post-cancellation grace, and returns CustomerContext.
    """
    from backend.dashboard.session import (
        get_cookie_name,
        get_session,
        touch_session,
    )
    from backend.shared.firestore import get_db
    from backend.shared.tier_config import TIER_CONFIG

    cookie_name = get_cookie_name()
    encrypted_cookie = request.cookies.get(cookie_name)
    if not encrypted_cookie:
        raise DashboardAuthError(
            redirect=True,
            htmx=_is_htmx(request),
        )

    session = await get_session(encrypted_cookie)
    if session is None:
        raise DashboardAuthError(
            redirect=True,
            htmx=_is_htmx(request),
        )

    email = session.get("email", "")
    session_id = session.get("session_id", "")

    # Check IP anomaly (log only, do not invalidate)
    if request.client:
        current_ip_hash = hashlib.sha256(
            request.client.host.encode(),
        ).hexdigest()
        if current_ip_hash != session.get("ip_hash"):
            _log.warning(
                "IP anomaly for session %s: expected %s, got %s",
                session_id[:8],
                session.get("ip_hash", "")[:8],
                current_ip_hash[:8],
            )

    # Find customer
    db = get_db()
    query = db.collection("customers").where("email", "==", email).limit(1)
    docs = [doc async for doc in query.stream()]
    if not docs:
        raise DashboardAuthError(
            redirect=True,
            htmx=_is_htmx(request),
        )

    customer_id = docs[0].id
    customer: dict[str, Any] = docs[0].to_dict()  # type: ignore[assignment]
    status = customer.get("status", "")

    if status not in ("active", "past_due", "cancelled"):
        raise DashboardAuthError(
            redirect=True,
            htmx=_is_htmx(request),
        )

    tier = customer.get("tier", "community")
    tier_cfg = TIER_CONFIG.get(tier, TIER_CONFIG["community"])
    features: list[str] = [str(f) for f in tier_cfg.get("features", [])]

    if "hosted_dashboard" not in features:
        raise DashboardAuthError(
            redirect=False,
            message=(
                f"Dashboard not available on {tier} tier."
                " Upgrade at https://complyform.dev/pricing"
            ),
        )

    # Post-cancellation logic
    role = "owner"
    cancellation_pending = False
    sub_expires = customer.get("subscription_expires_at", "")

    if status == "cancelled":
        cancellation_pending = True
        if sub_expires:
            try:
                exp = datetime.fromisoformat(sub_expires)
                now = datetime.now(UTC)
                grace_end = exp + timedelta(days=_GRACE_PERIOD_DAYS)
                if now > grace_end:
                    raise DashboardAuthError(
                        redirect=False,
                        message=(
                            "Dashboard access expired."
                            " Resubscribe at"
                            " https://complyform.dev/pricing"
                        ),
                    )
                if now > exp:
                    role = "viewer"
            except DashboardAuthError:
                raise
            except ValueError, TypeError:
                pass

    # Step 7: IP allowlist check (Enterprise+ only)
    if "ip_allowlist" in features:
        from backend.dashboard.ip_allowlist import (
            check_ip_allowed,
            get_ip_allowlist,
        )

        ip_config = await get_ip_allowlist(customer_id)
        if ip_config and ip_config.enabled:
            client_ip = request.client.host if request.client else ""
            if not check_ip_allowed(client_ip, ip_config):
                from fastapi import HTTPException

                raise HTTPException(
                    status_code=403,
                    detail=("Access restricted by your organization's IP policy."),
                )

    # Project / batch limits
    project_limit_raw = tier_cfg.get("project_limit")
    project_limit = int(project_limit_raw) if project_limit_raw is not None else None
    batch_limit_raw = tier_cfg.get("batch_limit")
    batch_limit = int(batch_limit_raw) if batch_limit_raw is not None else None

    # Consent
    consent_accepted = customer.get("consent_accepted", False)
    tos_version = customer.get("tos_version", "")
    privacy_version = customer.get("privacy_version", "")

    # Slide session expiry
    await touch_session(session_id)

    return CustomerContext(
        customer_id=customer_id,
        email=email,
        tier=tier,
        role=role,
        member_email=email,
        features=features,
        project_limit=project_limit,
        batch_limit=batch_limit,
        cancellation_pending=cancellation_pending,
        subscription_expires_at=str(sub_expires),
        consent_accepted=bool(consent_accepted),
        tos_version=str(tos_version),
        privacy_version=str(privacy_version),
    )


def dashboard_auth_error_response(
    exc: DashboardAuthError,
    request: Request,
) -> RedirectResponse | HTMLResponse | JSONResponse:
    """Convert DashboardAuthError to appropriate response."""
    if exc.redirect:
        return _build_redirect(request)
    return JSONResponse({"error": exc.message}, status_code=403)
