"""Public signup endpoints - no auth required.

Used by the marketing site (P2HS-Home) to power the hosted signup form.

GET  /api/public/signup-config   Plans list + field schema for the signup form.
POST /api/public/register        Create PendingRegistration + Stripe Checkout Session.
                                  Returns checkout_url so the browser can redirect to Stripe.
POST /api/public/contact         Website contact form (honeypot + persist + admin notify).
"""
from __future__ import annotations

import re
from datetime import datetime, timedelta, timezone
from typing import Optional

import structlog
from fastapi import APIRouter, Depends, HTTPException, Request
from pydantic import BaseModel, EmailStr
from sqlalchemy.ext.asyncio import AsyncSession

from src.config import get_settings
from src.core.database import get_db
from src.core.exceptions import ValidationError
from src.core.redis import get_redis
from src.core.turnstile import verify_turnstile
from src.domain.auth.request_throttle import enforce_auth_email_limits
from src.domain.admin.repository import SubscriptionPlanRepository
from src.domain.auth.repository import PendingRegistrationRepository, UserRepository
from src.domain.billing.constants import ENTERPRISE_PLAN_SLUG, STAFF_PLAN_SLUG
from src.domain.billing.credit_policy import email_is_staff
from src.domain.billing.campaign_repository import PricingCampaignRepository
from src.domain.billing.campaign_service import PricingCampaignService, email_subject_key
from src.domain.billing.plan_public import subscription_plan_response_for_public
from src.domain.billing.service import campaign_checkout_metadata
from src.domain.admin.schemas import SubscriptionPlanResponse
from src.domain.public.repository import ContactInquiryRepository
from src.domain.public.schemas import PublicContactRequest, PublicContactResponse
from src.domain.public.service import ContactInquiryService
from src.domain.templates.repository import TemplateRepository
from src.domain.templates.schemas import PublicTemplateListResponse, PublicTemplateResponse
from src.domain.templates.service import TemplateService
from src.domain.workspaces.repository import WorkspaceInviteRepository
from src.integrations.ops_notifier import get_ops_notifier

logger = structlog.get_logger()

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


# ---------------------------------------------------------------------------
# Response / request schemas
# ---------------------------------------------------------------------------

class SignupFieldSchema(BaseModel):
    name: str
    label: str
    type: str  # "email" | "text" | "tel"
    required: bool
    placeholder: Optional[str] = None
    max_length: Optional[int] = None


class SignupConfigResponse(BaseModel):
    plans: list[SubscriptionPlanResponse]
    fields: list[SignupFieldSchema]


class PublicRegisterRequest(BaseModel):
    email: EmailStr
    plan_slug: Optional[str] = None
    interval: str = "month"   # "month" | "year"
    first_name: Optional[str] = None
    last_name: Optional[str] = None
    company: Optional[str] = None
    workspace_name: Optional[str] = None
    # URLs to override Stripe success/cancel redirect (must match APP_URL_WHITELIST)
    success_url: Optional[str] = None
    cancel_url: Optional[str] = None
    turnstile_token: Optional[str] = None


class PublicRegisterResponse(BaseModel):
    checkout_url: Optional[str] = None   # None for free plans
    email: str
    free_plan: bool = False
    next_step: str  # "stripe_checkout" | "verify_email"
    payment_required: bool = False
    requires_plan_selection: bool = False
    onboarding_reason: Optional[str] = None


# ---------------------------------------------------------------------------
# Field schema (static — could be dynamic in future)
# ---------------------------------------------------------------------------

_SIGNUP_FIELDS: list[SignupFieldSchema] = [
    SignupFieldSchema(name="email", label="Email address", type="email", required=True, placeholder="you@example.com", max_length=255),
    SignupFieldSchema(name="first_name", label="First name", type="text", required=False, placeholder="Jane", max_length=100),
    SignupFieldSchema(name="last_name", label="Last name", type="text", required=False, placeholder="Smith", max_length=100),
    SignupFieldSchema(name="company", label="Company", type="text", required=False, placeholder="Acme Inc.", max_length=255),
]


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _sanitize_name(val: Optional[str], max_len: int = 255) -> Optional[str]:
    if not val:
        return None
    return val.strip()[:max_len] or None


def _sanitize_phone(phone: Optional[str]) -> Optional[str]:
    if not phone:
        return None
    s = re.sub(r"[^\d+\-() ]", "", phone.strip())
    return s[:50] if s else None


def _auto_workspace_name(
    company: Optional[str],
    first_name: Optional[str],
    last_name: Optional[str],
    email: str,
) -> str:
    parts = [p for p in [company, first_name, last_name] if p]
    name = " ".join(parts).strip() if parts else email.split("@")[0]
    return name[:255]


# ---------------------------------------------------------------------------
# Endpoints
# ---------------------------------------------------------------------------

@router.get("/templates", response_model=PublicTemplateListResponse)
async def list_showcased_templates(db: AsyncSession = Depends(get_db)):
    """Return the live templates selected for the public marketing showcase."""
    templates = await TemplateService(TemplateRepository(db)).list_showcased()
    return PublicTemplateListResponse(items=templates, total=len(templates))


@router.get("/templates/{ref}", response_model=PublicTemplateResponse)
async def get_showcased_template(ref: str, db: AsyncSession = Depends(get_db)):
    """Return one live public template by stable reference."""
    template = await TemplateService(TemplateRepository(db)).get_showcased_by_ref(ref.strip().lower())
    if not template:
        raise HTTPException(status_code=404, detail="Template not found.")
    return template

@router.post("/contact", response_model=PublicContactResponse)
async def public_contact(
    body: PublicContactRequest,
    db: AsyncSession = Depends(get_db),
):
    """Accept a marketing-site contact form. Honeypot field ``website`` is discarded."""
    if (body.website or "").strip():
        logger.info("public_contact_honeypot_accepted")
        return PublicContactResponse(ok=True)

    company = _sanitize_name(body.company, 120)
    service = ContactInquiryService(ContactInquiryRepository(db), get_ops_notifier())
    row = await service.create(
        first_name=body.first_name.strip(),
        last_name=body.last_name.strip(),
        email=str(body.email).strip().lower(),
        company=company,
        subject=body.subject,
        message=body.message.strip(),
    )
    await db.commit()
    await service.notify_admins(row)
    logger.info("public_contact_created", inquiry_id=str(row.id), subject=row.subject)
    return PublicContactResponse(ok=True, id=row.id)


@router.get("/signup-config", response_model=SignupConfigResponse)
async def get_signup_config(db: AsyncSession = Depends(get_db)):
    """Return active plans and signup form field schema. No auth required."""
    repo = SubscriptionPlanRepository(db)
    plans = [
        p
        for p in await repo.list_all(active_only=True)
        if p.slug not in (STAFF_PLAN_SLUG, ENTERPRISE_PLAN_SLUG)
    ]
    offers = await PricingCampaignService(PricingCampaignRepository(db)).offers_for_plans(plans)
    public_plans = [subscription_plan_response_for_public(p, offers.get(p.id)) for p in plans]
    return SignupConfigResponse(plans=public_plans, fields=_SIGNUP_FIELDS)


@router.post("/register", response_model=PublicRegisterResponse)
async def public_register(
    body: PublicRegisterRequest,
    request: Request,
    db: AsyncSession = Depends(get_db),
    redis=Depends(get_redis),
):
    """Create a PendingRegistration and Stripe Checkout Session.

    For paid plans: returns ``checkout_url`` to redirect the user to Stripe.
    For free plans: creates the pending registration directly and returns next_step=verify_email.
    Idempotent on email: returns a fresh checkout session each time.
    """
    settings = get_settings()
    email = str(body.email).lower().strip()
    client_ip = request.headers.get("CF-Connecting-IP") or (request.client.host if request.client else None)
    await verify_turnstile(body.turnstile_token, action="signup", remote_ip=client_ip)
    await enforce_auth_email_limits(redis, email=email, ip_address=client_ip)

    invite_repo = WorkspaceInviteRepository(db)
    is_staff_signup = email_is_staff(email, settings.staff_email_domain_set)
    has_pending_invite = await invite_repo.has_pending_invite_for_email(email)
    bypass_payment = is_staff_signup or has_pending_invite

    if not bypass_payment and not body.plan_slug:
        raise ValidationError("Select an available plan to continue signup.")

    # Staff-domain and invite-first signups are sponsored by an existing
    # platform relationship. Do not let an arbitrary paid plan in the request
    # send either exception through Stripe.
    plan_repo = SubscriptionPlanRepository(db)
    plan = None
    if is_staff_signup:
        plan = await plan_repo.get_by_slug(STAFF_PLAN_SLUG)
        if not plan or not plan.is_active:
            raise ValidationError("Staff signup is not currently available.")
    elif not has_pending_invite:
        plan = await plan_repo.get_by_slug(body.plan_slug)
        if not plan or not plan.is_active:
            raise ValidationError(f"Invalid or inactive plan: {body.plan_slug}")
        if plan.slug in (ENTERPRISE_PLAN_SLUG, STAFF_PLAN_SLUG):
            raise ValidationError("This plan cannot be selected via self-serve signup.")

    first_name = _sanitize_name(body.first_name, 100)
    last_name = _sanitize_name(body.last_name, 100)
    company = _sanitize_name(body.company, 255)
    workspace_name = _sanitize_name(body.workspace_name, 255) or _auto_workspace_name(company, first_name, last_name, email)

    now = datetime.now(timezone.utc)
    pending_repo = PendingRegistrationRepository(db)
    user_repo = UserRepository(db)

    # If user already exists, fall back to standard login magic link flow
    existing_user = await user_repo.get_by_email(email)
    if existing_user:
        from src.domain.auth.service import AuthService
        from src.domain.auth.repository import SessionRepository, MagicLinkRepository
        from src.domain.auth.mfa_repository import TotpSecretRepository
        from src.integrations.email_provider import email_transport_configured, get_email_provider

        email_provider = get_email_provider() if email_transport_configured(settings) else None
        auth_svc = AuthService(
            user_repo=user_repo,
            session_repo=SessionRepository(db),
            magic_link_repo=MagicLinkRepository(db),
            totp_repo=TotpSecretRepository(db),
            email_provider=email_provider,
            pending_repo=pending_repo,
        )
        await auth_svc.request_magic_link(
            email,
            ip_address=request.client.host if request.client else None,
            app_base_url=settings.app_url,
            is_signup=False,
        )
        logger.info("public_register_existing_user_magic_link_sent", email=email)
        return PublicRegisterResponse(
            checkout_url=None,
            email=email,
            free_plan=bypass_payment,
            next_step="verify_email",
            payment_required=False,
            requires_plan_selection=False,
            onboarding_reason=("staff_domain" if is_staff_signup else "workspace_invitation" if has_pending_invite else None),
        )

    # Create / update pending registration (no magic link sent yet — sent after payment)
    expires_at = now + timedelta(hours=48)
    pending = await pending_repo.create_or_update(
        email,
        first_name=first_name,
        last_name=last_name,
        company=company,
        workspace_name=workspace_name,
        plan_slug=plan.slug if plan else None,
        expires_at=expires_at,
    )
    await db.flush()

    is_free_plan = bypass_payment or (plan.price_monthly == 0 and not plan.stripe_monthly_price_id)

    if is_free_plan:
        # Free plan: send magic link immediately, no Stripe step
        from src.domain.auth.service import AuthService
        from src.domain.auth.repository import SessionRepository, MagicLinkRepository
        from src.domain.auth.mfa_repository import TotpSecretRepository
        from src.integrations.email_provider import email_transport_configured, get_email_provider

        email_provider = get_email_provider() if email_transport_configured(settings) else None
        auth_svc = AuthService(
            user_repo=user_repo,
            session_repo=SessionRepository(db),
            magic_link_repo=MagicLinkRepository(db),
            totp_repo=TotpSecretRepository(db),
            email_provider=email_provider,
            pending_repo=pending_repo,
        )
        await auth_svc.request_magic_link(
            email,
            ip_address=request.client.host if request.client else None,
            app_base_url=settings.app_url,
            allow_unknown_email=True,
            is_signup=True,
        )
        await db.commit()
        logger.info("public_register_free_plan_magic_link_sent", email=email, plan=plan.slug)
        return PublicRegisterResponse(
            checkout_url=None,
            email=email,
            free_plan=True,
            next_step="verify_email",
            payment_required=False,
            requires_plan_selection=False,
            onboarding_reason=("staff_domain" if is_staff_signup else "workspace_invitation" if has_pending_invite else None),
        )

    # Paid plan: create Stripe customer and checkout session
    stripe_client = _get_stripe_client()
    if not stripe_client:
        raise ValidationError("Payment processing is not configured. Please contact support.")

    # Reuse existing Stripe customer if this pending reg already has one
    stripe_customer_id = pending.stripe_customer_id
    if not stripe_customer_id:
        stripe_customer_id = await stripe_client.create_customer(
            email,
            workspace_name or email.split("@")[0],
        )

    # Resolve success/cancel URLs.
    # This endpoint is called server-to-server from the home-site Worker, not from a
    # browser, so we bypass whitelist validation. Success always points to the dashboard;
    # cancel falls back to the home-site signup page via settings.home_url.
    dashboard_url = settings.app_url.rstrip("/")
    home_url = getattr(settings, "home_url", dashboard_url).rstrip("/")
    from urllib.parse import quote
    success_url = body.success_url or f"{dashboard_url}/auth/welcome?email={quote(email)}"
    cancel_url = body.cancel_url or f"{home_url}/signup"

    interval = body.interval if body.interval in ("month", "year") else "month"
    from src.domain.billing.catalog_service import BillingCatalogService
    from src.domain.billing.repository import UsageCreditPackageRepository
    from src.domain.admin.repository import PlanPriceVersionRepository

    catalog = BillingCatalogService(
        plan_repo=plan_repo,
        package_repo=UsageCreditPackageRepository(db),
        price_version_repo=PlanPriceVersionRepository(db),
    )
    price_id = catalog.resolve_stripe_price_id_for_plan(plan, interval)

    campaigns = PricingCampaignService(PricingCampaignRepository(db))
    reservation = await campaigns.reserve(
        plan=plan, interval=interval, subject_key=email_subject_key(email)
    )
    if reservation:
        price_id = reservation.stripe_price_id

    metadata = {
        "pending_registration_id": str(pending.id),
        "email": email,
        "plan_slug": plan.slug,
        "interval": interval,
        "purchase_type": "subscription",
        "onboarding_intent": "pre_registration",
        **campaign_checkout_metadata(reservation),
    }
    checkout_url, session_id = await stripe_client.create_subscription_checkout(
        stripe_customer_id, price_id, success_url, cancel_url, metadata
    )
    if reservation:
        await campaigns.attach_session(reservation.redemption_id, session_id)

    # Stripe checkout sessions expire in 24 hours by default
    checkout_expires_at = now + timedelta(hours=24)
    await pending_repo.update_checkout(
        pending.id,
        checkout_session_id=session_id,
        checkout_expires_at=checkout_expires_at,
        stripe_customer_id=stripe_customer_id,
    )
    await db.commit()

    logger.info(
        "public_register_checkout_created",
        email=email,
        plan=plan.slug,
        session_id=session_id,
        pending_id=str(pending.id),
    )
    return PublicRegisterResponse(
        checkout_url=checkout_url,
        email=email,
        free_plan=False,
        next_step="stripe_checkout",
        payment_required=True,
        requires_plan_selection=False,
    )


def _get_stripe_client():
    from src.integrations.stripe_client import StripeClientImpl
    settings = get_settings()
    if settings.stripe_secret_key:
        return StripeClientImpl()
    return None
