"""Billing domain - FastAPI router.

Endpoints:
  GET  /api/billing/plans                  Available plans for workspace (no Enterprise row; yearly pricing semantics)
  POST /api/billing/enterprise-inquiry     Sales lead form (guest OK; optional Bearer + workspace_id)
  POST /api/billing/checkout-session        Create Stripe checkout for subscription (plan_slug + interval)
  POST /api/billing/checkout-session/topup Create Stripe checkout for usage credit top-up
  POST /api/billing/portal                 Get Stripe customer portal URL
  POST /api/billing/stripe/webhook         Stripe webhook receiver (sig verified)
  GET  /api/billing/subscription           Current workspace subscription
  GET  /api/billing/checkout-status        Poll subscription after Stripe return (200, normalized)
  GET  /api/billing/invoices                Workspace invoices
  GET  /api/billing/usage                   Usage summary for current billing period
"""
from __future__ import annotations
from typing import Optional
from uuid import UUID

import structlog
from fastapi import APIRouter, Body, Depends, Header, Query, Request, status
from sqlalchemy.ext.asyncio import AsyncSession

from src.config import get_settings
from src.core.app_url import resolve_checkout_redirect_urls
from src.core.database import get_db
from src.core.exceptions import ForbiddenError, ValidationError
from src.domain.admin.repository import PlanPriceVersionRepository, SubscriptionPlanRepository
from src.domain.auth.models import User
from src.domain.auth.router import get_current_user, get_current_user_optional
from src.domain.billing.constants import ENTERPRISE_PLAN_SLUG, STAFF_PLAN_SLUG
from src.domain.billing.campaign_repository import PricingCampaignRepository
from src.domain.billing.campaign_service import PricingCampaignService
from src.domain.billing.catalog_service import BillingCatalogService
from src.domain.billing.repository import (
    EnterpriseInquiryRepository,
    InvoiceRepository,
    StripeCustomerRepository,
    StripeEventRepository,
    SubscriptionRepository,
    UsageCreditPackageRepository,
    UsageCreditPurchaseRepository,
    UsageLedgerRepository,
    WorkspaceCreditLedgerRepository,
    WorkspaceUsageAllowanceRepository,
)
from src.domain.billing.plan_public import subscription_plan_to_public
from src.domain.billing.schemas import (
    AvailablePlansResponse,
    BillingCheckoutStatusResponse,
    CheckoutSessionResponse,
    CustomerPortalRequest,
    CustomerPortalResponse,
    EnterpriseInquiryCreate,
    EnterpriseInquiryResponse,
    InvoiceResponse,
    SubscriptionPlanPublic,
    SubscriptionResponse,
    SubscriptionCheckoutCreate,
    UsageTopupCheckoutCreate,
    UsageCreditPackagePublic,
    WorkspaceBalanceResponse,
)
from src.domain.billing.inquiry_service import EnterpriseInquiryService
from src.domain.billing.service import BillingService
from src.domain.workspaces.facades import WorkspaceFacadeImpl
from src.domain.workspaces.repository import MembershipRepository, WorkspaceRepository

logger = structlog.get_logger()
from src.integrations.stripe_client import StripeClientImpl
from src.integrations.ops_notifier import get_ops_notifier
from src.workers.enqueue import enqueue_billing_email, enqueue_pre_registration_complete

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


def _enrich_subscription_response(sub) -> SubscriptionResponse:
    base = SubscriptionResponse.model_validate(sub)
    paid = base.status in ("active", "trialing")
    return base.model_copy(
        update={
            "has_subscription": True,
            "is_paid": paid,
            "requires_payment": not paid,
            "billing_status": base.status,
        }
    )


async def get_workspace_facade(db: AsyncSession = Depends(get_db)) -> WorkspaceFacadeImpl:
    return WorkspaceFacadeImpl(WorkspaceRepository(db), MembershipRepository(db))


def _get_stripe_client() -> Optional[StripeClientImpl]:
    settings = get_settings()
    if settings.stripe_secret_key:
        return StripeClientImpl()
    return None


async def get_billing_catalog(db: AsyncSession = Depends(get_db)) -> BillingCatalogService:
    return BillingCatalogService(
        plan_repo=SubscriptionPlanRepository(db),
        package_repo=UsageCreditPackageRepository(db),
        price_version_repo=PlanPriceVersionRepository(db),
    )


async def get_billing_service(db: AsyncSession = Depends(get_db)) -> BillingService:
    return BillingService(
        customer_repo=StripeCustomerRepository(db),
        subscription_repo=SubscriptionRepository(db),
        invoice_repo=InvoiceRepository(db),
        event_repo=StripeEventRepository(db),
        usage_repo=UsageLedgerRepository(db),
        purchase_repo=UsageCreditPurchaseRepository(db),
        ledger_repo=WorkspaceCreditLedgerRepository(db),
        catalog=BillingCatalogService(
            plan_repo=SubscriptionPlanRepository(db),
            package_repo=UsageCreditPackageRepository(db),
            price_version_repo=PlanPriceVersionRepository(db),
        ),
        stripe_client=_get_stripe_client(),
        enqueue_billing_email=enqueue_billing_email,
        workspace_repo=WorkspaceRepository(db),
        enqueue_pre_registration=enqueue_pre_registration_complete,
        allowance_repo=WorkspaceUsageAllowanceRepository(db),
        campaign_service=PricingCampaignService(PricingCampaignRepository(db)),
    )


@router.get("/plans", response_model=AvailablePlansResponse)
async def get_available_plans(
    workspace_id: UUID = Query(...),
    user: User = Depends(get_current_user),
    catalog: BillingCatalogService = Depends(get_billing_catalog),
    service: BillingService = Depends(get_billing_service),
    facade: WorkspaceFacadeImpl = Depends(get_workspace_facade),
):
    """List sellable self-serve plans for workspace (Enterprise excluded — use ``POST /enterprise-inquiry``)."""
    if not await facade.check_permission(workspace_id, user.id, "invoices.read"):
        raise ForbiddenError("Workspace billing access required")
    plans = [
        p
        for p in await catalog.list_plans(active_only=True)
        if p.slug.lower() not in (ENTERPRISE_PLAN_SLUG, STAFF_PLAN_SLUG)
    ]
    current_sub = await service.get_subscription_optional(workspace_id)
    offers = await service.get_plan_offers(workspace_id, plans)
    return AvailablePlansResponse(
        plans=[subscription_plan_to_public(p, offers.get(p.id)) for p in plans],
        current_subscription=_enrich_subscription_response(current_sub) if current_sub else None,
    )


@router.post(
    "/enterprise-inquiry",
    status_code=status.HTTP_201_CREATED,
    response_model=EnterpriseInquiryResponse,
)
async def create_enterprise_inquiry(
    request: Request,
    body: EnterpriseInquiryCreate,
    workspace_id: Optional[UUID] = Query(
        None,
        description="Optional; if sent with Authorization, caller must have billing.manage on this workspace.",
    ),
    db: AsyncSession = Depends(get_db),
    user: Optional[User] = Depends(get_current_user_optional),
):
    """Persist a sales / Enterprise inquiry. Guests may submit without auth.

    When ``workspace_id`` and a valid Bearer token are both present, membership
    ``billing.manage`` is required so users cannot attach inquiries to arbitrary workspaces.
    """
    if workspace_id is not None and user is not None:
        facade = WorkspaceFacadeImpl(WorkspaceRepository(db), MembershipRepository(db))
        if not await facade.check_permission(workspace_id, user.id, "billing.manage"):
            raise ForbiddenError("Workspace billing access required")

    service = EnterpriseInquiryService(EnterpriseInquiryRepository(db), get_ops_notifier())
    row = await service.create(
        workspace_id=workspace_id,
        user_id=user.id if user else None,
        first_name=body.first_name.strip(),
        last_name=body.last_name.strip(),
        email=str(body.email).strip().lower(),
        phone=body.phone.strip(),
        website_count=body.website_count,
        ai_credit_minutes_desired=body.ai_credit_minutes_desired,
        is_agency_account=body.is_agency_account,
        employee_count=body.employee_count,
        additional_features=(
            (body.additional_features.strip() or None) if body.additional_features else None
        ),
        plan_slug=(body.plan_slug.strip().lower() if body.plan_slug else None),
    )
    await db.commit()
    await service.notify_admins(row)
    logger.info(
        "enterprise_inquiry_created",
        inquiry_id=str(row.id),
        workspace_id=str(workspace_id) if workspace_id else None,
        authenticated=bool(user),
        client_host=request.client.host if request.client else None,
    )
    return EnterpriseInquiryResponse(id=row.id)


@router.post("/checkout-session", response_model=CheckoutSessionResponse)
async def create_subscription_checkout(
    request: Request,
    body: SubscriptionCheckoutCreate,
    workspace_id: UUID = Query(...),
    user: User = Depends(get_current_user),
    service: BillingService = Depends(get_billing_service),
    facade: WorkspaceFacadeImpl = Depends(get_workspace_facade),
):
    """Create Stripe checkout for subscription - uses internal plan_slug.

    success_url/cancel_url: Optional. When omitted, derived from Origin or
    X-CLIENT-URL / X-CLIENT-DOMAIN (multi-frontend support).
    """
    if not await facade.check_permission(workspace_id, user.id, "billing.manage"):
        raise ForbiddenError("Workspace billing access required")
    success_url, cancel_url = resolve_checkout_redirect_urls(
        request, body.success_url, body.cancel_url
    )
    ws_summary = await facade.get_workspace_summary(workspace_id)
    workspace_name = ws_summary.name if ws_summary else ""
    result = await service.start_subscription_checkout(
        workspace_id=workspace_id,
        user_id=user.id,
        email=user.email,
        workspace_name=workspace_name,
        plan_slug=body.plan_slug,
        interval=body.interval,
        success_url=success_url,
        cancel_url=cancel_url,
        idempotency_key=body.idempotency_key or request.headers.get("Idempotency-Key"),
    )
    return CheckoutSessionResponse(
        checkout_url=result.get("checkout_url"),
        session_id=result.get("session_id"),
        workspace_id=result.get("workspace_id"),
        free_plan=bool(result.get("free_plan")),
    )


@router.post("/checkout-session/topup", response_model=CheckoutSessionResponse)
async def create_usage_topup_checkout(
    request: Request,
    body: UsageTopupCheckoutCreate,
    workspace_id: UUID = Query(...),
    user: User = Depends(get_current_user),
    service: BillingService = Depends(get_billing_service),
    facade: WorkspaceFacadeImpl = Depends(get_workspace_facade),
):
    """Create Stripe checkout for one-time usage credit purchase.

    success_url/cancel_url: Optional. Same resolution as subscription checkout.
    """
    if not await facade.check_permission(workspace_id, user.id, "billing.manage"):
        raise ForbiddenError("Workspace billing access required")
    success_url, cancel_url = resolve_checkout_redirect_urls(
        request, body.success_url, body.cancel_url
    )
    ws_summary = await facade.get_workspace_summary(workspace_id)
    workspace_name = ws_summary.name if ws_summary else ""
    result = await service.start_usage_topup_checkout(
        workspace_id=workspace_id,
        user_id=user.id,
        email=user.email,
        workspace_name=workspace_name,
        package_slug=body.package_slug,
        success_url=success_url,
        cancel_url=cancel_url,
        idempotency_key=body.idempotency_key or request.headers.get("Idempotency-Key"),
    )
    return CheckoutSessionResponse(
        checkout_url=result["checkout_url"],
        session_id=result["session_id"],
        workspace_id=result.get("workspace_id"),
    )


@router.post("/portal", response_model=CustomerPortalResponse)
async def customer_portal(
    request: Request,
    body: Optional[CustomerPortalRequest] = Body(None),
    workspace_id: UUID = Query(...),
    user: User = Depends(get_current_user),
    service: BillingService = Depends(get_billing_service),
    facade: WorkspaceFacadeImpl = Depends(get_workspace_facade),
):
    """Get Stripe customer portal URL. return_url optional; derived from Origin/X-CLIENT-* when omitted."""
    if not await facade.check_permission(workspace_id, user.id, "billing.manage"):
        raise ForbiddenError("Workspace billing access required")
    from src.core.app_url import resolve_single_redirect_url
    return_url = resolve_single_redirect_url(
        request,
        url=getattr(body, "return_url", None) if body else None,
        default_path="/billing",
    )
    url = await service.create_customer_portal_session(workspace_id, return_url=return_url)
    return CustomerPortalResponse(portal_url=url)


@router.post("/stripe/webhook")
async def stripe_webhook(
    request: Request,
    service: BillingService = Depends(get_billing_service),
    stripe_signature: Optional[str] = Header(None, alias="Stripe-Signature"),
):
    body = await request.body()
    settings = get_settings()
    stripe_client = _get_stripe_client()
    stripe_configured = bool(stripe_client and settings.stripe_webhook_secret)

    if stripe_configured:
        # Production: require signature and verify
        if not stripe_signature:
            if settings.stripe_webhook_allow_unsigned:
                import json
                parsed = json.loads(body)
                event_id = parsed.get("id", "")
                event_type = parsed.get("type", "")
                data = parsed.get("data", {})
            else:
                raise ForbiddenError("Stripe-Signature header required")
        else:
            try:
                event_data = stripe_client.verify_webhook_signature(body, stripe_signature)
                event_id = event_data["id"]
                event_type = event_data["type"]
                data = event_data["data"]
            except Exception:
                raise ForbiddenError("Invalid Stripe webhook signature")
    else:
        if settings.stripe_webhook_allow_unsigned:
            import json
            parsed = json.loads(body)
            event_id = parsed.get("id", "")
            event_type = parsed.get("type", "")
            data = parsed.get("data", {})
        else:
            raise ForbiddenError("Stripe webhook not configured")

    result = await service.handle_webhook_event(event_id, event_type, data)
    return {"received": True, **result}


@router.get("/subscription", response_model=SubscriptionResponse)
async def get_subscription(
    workspace_id: UUID = Query(...),
    user: User = Depends(get_current_user),
    service: BillingService = Depends(get_billing_service),
    facade: WorkspaceFacadeImpl = Depends(get_workspace_facade),
):
    if not await facade.check_permission(workspace_id, user.id, "invoices.read"):
        raise ForbiddenError("Workspace billing access required")
    sub = await service.get_subscription(workspace_id)
    return _enrich_subscription_response(sub)


@router.get("/checkout-status", response_model=BillingCheckoutStatusResponse)
async def get_checkout_status(
    workspace_id: UUID = Query(...),
    checkout_return: Optional[str] = Query(
        None,
        description="Pass `success` or `canceled` on first poll from return routes; persisted for later polls.",
    ),
    user: User = Depends(get_current_user),
    service: BillingService = Depends(get_billing_service),
    facade: WorkspaceFacadeImpl = Depends(get_workspace_facade),
    db: AsyncSession = Depends(get_db),
):
    if not await facade.check_permission(workspace_id, user.id, "invoices.read"):
        raise ForbiddenError("Workspace billing access required")

    normalized: Optional[str] = None
    if checkout_return is not None:
        cr = checkout_return.strip().lower()
        if cr == "success":
            normalized = "success_return"
        elif cr in ("canceled", "cancelled"):
            normalized = "canceled_return"
        else:
            raise ValidationError("checkout_return must be success or canceled")
        await WorkspaceRepository(db).update_billing_checkout_tracking(
            workspace_id, checkout_return=normalized
        )
        logger.info(
            "checkout_return_verification_poll",
            workspace_id=str(workspace_id),
            user_id=str(user.id),
            checkout_return=normalized,
        )

    ws = await WorkspaceRepository(db).get_by_id(workspace_id)
    stored_return = getattr(ws, "billing_last_checkout_return", None) if ws else None
    payload = await service.build_checkout_status_payload(
        workspace_id,
        stored_return,
        normalized,
    )
    return BillingCheckoutStatusResponse(**payload)


@router.get("/invoices", response_model=list[InvoiceResponse])
async def list_invoices(
    workspace_id: UUID = Query(...),
    user: User = Depends(get_current_user),
    service: BillingService = Depends(get_billing_service),
    facade: WorkspaceFacadeImpl = Depends(get_workspace_facade),
):
    if not await facade.check_permission(workspace_id, user.id, "invoices.read"):
        raise ForbiddenError("Workspace billing access required")
    invoices = await service.list_invoices(workspace_id)
    return [InvoiceResponse.model_validate(i) for i in invoices]


@router.get("/usage")
async def get_usage_summary(
    workspace_id: UUID = Query(...),
    user: User = Depends(get_current_user),
    service: BillingService = Depends(get_billing_service),
    facade: WorkspaceFacadeImpl = Depends(get_workspace_facade),
):
    if not await facade.check_permission(workspace_id, user.id, "invoices.read"):
        raise ForbiddenError("Workspace billing access required")
    return await service.get_usage_summary(workspace_id)


@router.get("/balance", response_model=WorkspaceBalanceResponse)
async def get_workspace_balance(
    workspace_id: UUID = Query(...),
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
    facade: WorkspaceFacadeImpl = Depends(get_workspace_facade),
):
    """Current credit balance + purchasable packages for the workspace.

    Returns included plan allowance remaining, purchased top-up balance,
    and the full list of available credit packages the user can buy.
    Use when credits are exhausted to show the top-up UI.
    """
    if not await facade.check_permission(workspace_id, user.id, "invoices.read"):
        raise ForbiddenError("Workspace billing access required")

    from src.domain.billing.balance_service import WorkspaceUsageBalanceService
    from src.domain.billing.repository import WorkspaceUsageAllowanceRepository

    balance_svc = WorkspaceUsageBalanceService(
        subscription_repo=SubscriptionRepository(db),
        plan_repo=SubscriptionPlanRepository(db),
        allowance_repo=WorkspaceUsageAllowanceRepository(db),
        ledger_repo=WorkspaceCreditLedgerRepository(db),
    )
    balance = await balance_svc.get_balance(workspace_id)
    packages = await UsageCreditPackageRepository(db).list_all(active_only=True)

    return WorkspaceBalanceResponse(
        included_remaining=balance.included_allowance_remaining,
        topup_remaining=balance.purchased_topup_remaining,
        total_available=balance.total_available,
        has_active_subscription=balance.has_active_subscription,
        plan_slug=balance.plan_slug,
        packages=[
            UsageCreditPackagePublic(
                slug=p.slug,
                name=p.name,
                description=p.description,
                units=p.units,
                price_minor=p.price_minor,
                currency=p.currency,
            )
            for p in packages
        ],
    )
