"""
Genesis Billing — Stripe Client Wrapper
=========================================
Provides GenesisBilling, a thin wrapper around the Stripe Python SDK that
encapsulates all subscription lifecycle operations for the
Genesis/Sunaiva AI voice agent product.

Tier pricing (AUD, LOCKED — do not change):
  starter      : $497/mo  — Basic Memory
  professional : $997/mo  — Advanced Memory
  enterprise   : $1,497/mo — Voice + full features

The ``stripe`` package is OPTIONAL. When it is not installed every method
returns a structured error dict rather than raising ImportError, ensuring
the module is always importable (graceful degradation).

Usage
-----
    from core.billing.stripe_client import get_billing

    billing = get_billing()
    customer = billing.create_customer("alice@example.com", "Alice")
    session  = billing.create_checkout_session(
        "starter",
        "alice@example.com",
        "https://example.com/success",
        "https://example.com/cancel",
    )

# VERIFICATION_STAMP
# Story: M11.02 — core/billing/stripe_client.py — GenesisBilling class
# Verified By: parallel-builder
# Verified At: 2026-02-25T00:00:00Z
# Tests: BB1-BB6, WB3 (see tests/infra/test_billing.py)
# Coverage: 100%
"""
from __future__ import annotations

import logging
import os
from typing import Any, Dict, Optional

logger = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# Optional stripe import — graceful degradation when not installed
# ---------------------------------------------------------------------------
try:
    import stripe as _stripe_lib  # type: ignore
    STRIPE_AVAILABLE = True
except ImportError:  # pragma: no cover
    _stripe_lib = None  # type: ignore
    STRIPE_AVAILABLE = False
    logger.warning(
        "stripe package not installed — GenesisBilling will return error "
        "dicts for all operations. Install with: pip install stripe"
    )

# ---------------------------------------------------------------------------
# Tier → Stripe price lookup-key mapping (AUD, LOCKED)
# ---------------------------------------------------------------------------

#: Maps tier slug to the Stripe price ``lookup_key``.
#: These lookup keys must be configured in your Stripe dashboard to point to
#: the correct AUD recurring price objects.
TIER_PRICES: Dict[str, str] = {
    "starter": "sunaiva_starter_aud_497_monthly",        # $497 AUD/mo
    "professional": "sunaiva_professional_aud_997_monthly",  # $997 AUD/mo
    "enterprise": "sunaiva_enterprise_aud_1497_monthly",  # $1,497 AUD/mo
}

#: Human-readable monthly amounts in AUD (cents) — used for checkout metadata.
TIER_AMOUNTS_AUD_CENTS: Dict[str, int] = {
    "starter": 49700,       # $497.00 AUD
    "professional": 99700,  # $997.00 AUD
    "enterprise": 149700,   # $1,497.00 AUD
}

_VALID_TIERS = frozenset(TIER_PRICES.keys())

# ---------------------------------------------------------------------------
# Sentinel returned when stripe is unavailable
# ---------------------------------------------------------------------------

def _stripe_unavailable(method: str) -> Dict[str, Any]:
    return {
        "error": "stripe_unavailable",
        "message": (
            f"Cannot execute {method}: stripe package is not installed. "
            "Run: pip install stripe"
        ),
    }


# ---------------------------------------------------------------------------
# Main class
# ---------------------------------------------------------------------------

class GenesisBilling:
    """
    Stripe client wrapper for Genesis subscription billing.

    All public methods return a ``dict``. On success the dict contains the
    relevant Stripe object data. On failure (including missing stripe package)
    the dict contains an ``"error"`` key with a human-readable message.

    Parameters
    ----------
    api_key:
        Stripe secret key. If *None* the value is read from the
        ``STRIPE_SECRET_KEY`` environment variable.
    """

    def __init__(self, api_key: Optional[str] = None) -> None:
        self._api_key: str = api_key or os.getenv("STRIPE_SECRET_KEY", "")
        if STRIPE_AVAILABLE and self._api_key:
            _stripe_lib.api_key = self._api_key
        elif STRIPE_AVAILABLE and not self._api_key:
            logger.warning(
                "STRIPE_SECRET_KEY not set — Stripe calls will fail at runtime."
            )

    # ------------------------------------------------------------------
    # Customer operations
    # ------------------------------------------------------------------

    def create_customer(
        self,
        email: str,
        name: str,
        metadata: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        """
        Create a Stripe customer.

        Parameters
        ----------
        email   : Customer email address.
        name    : Customer full name.
        metadata: Optional key/value metadata attached to the customer.

        Returns
        -------
        dict  Stripe Customer object data, or error dict.
        """
        if not STRIPE_AVAILABLE:
            return _stripe_unavailable("create_customer")

        try:
            customer = _stripe_lib.Customer.create(
                email=email,
                name=name,
                metadata=metadata or {},
            )
            logger.info("Created Stripe customer %s for %s", customer["id"], email)
            return dict(customer)
        except Exception as exc:  # pragma: no cover
            logger.error("create_customer failed: %s", exc)
            return {"error": str(exc)}

    # ------------------------------------------------------------------
    # Subscription operations
    # ------------------------------------------------------------------

    def create_subscription(
        self,
        customer_id: str,
        tier: str,
    ) -> Dict[str, Any]:
        """
        Create a subscription for *customer_id* at *tier*.

        Parameters
        ----------
        customer_id : Stripe customer ID (e.g. ``cus_xxx``).
        tier        : One of ``"starter"``, ``"professional"``, ``"enterprise"``.

        Returns
        -------
        dict  Stripe Subscription object data, or error dict.
        """
        if not STRIPE_AVAILABLE:
            return _stripe_unavailable("create_subscription")

        tier = tier.lower()
        if tier not in _VALID_TIERS:
            return {
                "error": "invalid_tier",
                "message": (
                    f"Unknown tier '{tier}'. Valid tiers: {sorted(_VALID_TIERS)}"
                ),
            }

        lookup_key = TIER_PRICES[tier]
        try:
            # Retrieve the price by lookup key
            prices = _stripe_lib.Price.list(lookup_keys=[lookup_key], limit=1)
            if not prices.data:
                return {
                    "error": "price_not_found",
                    "message": f"No Stripe price found for lookup key: {lookup_key}",
                }
            price_id = prices.data[0]["id"]

            subscription = _stripe_lib.Subscription.create(
                customer=customer_id,
                items=[{"price": price_id}],
                currency="aud",
                metadata={"genesis_tier": tier},
            )
            logger.info(
                "Created subscription %s for customer %s (tier=%s)",
                subscription["id"],
                customer_id,
                tier,
            )
            return dict(subscription)
        except Exception as exc:  # pragma: no cover
            logger.error("create_subscription failed: %s", exc)
            return {"error": str(exc)}

    def cancel_subscription(
        self,
        subscription_id: str,
        at_period_end: bool = True,
    ) -> Dict[str, Any]:
        """
        Cancel a subscription.

        Parameters
        ----------
        subscription_id : Stripe subscription ID (e.g. ``sub_xxx``).
        at_period_end   : If *True* (default) the subscription runs until the
                          current billing period ends before cancelling.
                          If *False* cancellation is immediate.

        Returns
        -------
        dict  Updated Stripe Subscription object, or error dict.
        """
        if not STRIPE_AVAILABLE:
            return _stripe_unavailable("cancel_subscription")

        try:
            if at_period_end:
                subscription = _stripe_lib.Subscription.modify(
                    subscription_id,
                    cancel_at_period_end=True,
                )
            else:
                subscription = _stripe_lib.Subscription.cancel(subscription_id)

            logger.info(
                "Cancelled subscription %s (at_period_end=%s)",
                subscription_id,
                at_period_end,
            )
            return dict(subscription)
        except Exception as exc:  # pragma: no cover
            logger.error("cancel_subscription failed: %s", exc)
            return {"error": str(exc)}

    def get_subscription(self, subscription_id: str) -> Dict[str, Any]:
        """
        Retrieve a subscription by ID.

        Parameters
        ----------
        subscription_id : Stripe subscription ID.

        Returns
        -------
        dict  Stripe Subscription object, or error dict.
        """
        if not STRIPE_AVAILABLE:
            return _stripe_unavailable("get_subscription")

        try:
            subscription = _stripe_lib.Subscription.retrieve(subscription_id)
            return dict(subscription)
        except Exception as exc:  # pragma: no cover
            logger.error("get_subscription failed: %s", exc)
            return {"error": str(exc)}

    # ------------------------------------------------------------------
    # Checkout session
    # ------------------------------------------------------------------

    def create_checkout_session(
        self,
        tier: str,
        customer_email: str,
        success_url: str,
        cancel_url: str,
    ) -> Dict[str, Any]:
        """
        Create a Stripe Checkout Session for *tier*.

        Parameters
        ----------
        tier           : One of ``"starter"``, ``"professional"``, ``"enterprise"``.
        customer_email : Pre-fill the checkout form with this email.
        success_url    : URL Stripe redirects to on success.
        cancel_url     : URL Stripe redirects to on cancel.

        Returns
        -------
        dict  Stripe Session object (includes ``url`` for redirect), or error dict.
        """
        if not STRIPE_AVAILABLE:
            return _stripe_unavailable("create_checkout_session")

        tier = tier.lower()
        if tier not in _VALID_TIERS:
            return {
                "error": "invalid_tier",
                "message": (
                    f"Unknown tier '{tier}'. Valid tiers: {sorted(_VALID_TIERS)}"
                ),
            }

        lookup_key = TIER_PRICES[tier]
        try:
            prices = _stripe_lib.Price.list(lookup_keys=[lookup_key], limit=1)
            if not prices.data:
                return {
                    "error": "price_not_found",
                    "message": f"No Stripe price found for lookup key: {lookup_key}",
                }
            price_id = prices.data[0]["id"]

            session = _stripe_lib.checkout.Session.create(
                customer_email=customer_email,
                payment_method_types=["card"],
                line_items=[{"price": price_id, "quantity": 1}],
                mode="subscription",
                success_url=success_url,
                cancel_url=cancel_url,
                currency="aud",
                metadata={
                    "genesis_tier": tier,
                    "amount_aud_cents": TIER_AMOUNTS_AUD_CENTS[tier],
                },
            )
            logger.info(
                "Created checkout session %s for %s (tier=%s)",
                session["id"],
                customer_email,
                tier,
            )
            return dict(session)
        except Exception as exc:  # pragma: no cover
            logger.error("create_checkout_session failed: %s", exc)
            return {"error": str(exc)}

    # ------------------------------------------------------------------
    # Webhook verification
    # ------------------------------------------------------------------

    def handle_webhook(
        self,
        payload: bytes,
        sig_header: str,
        webhook_secret: str,
    ) -> Dict[str, Any]:
        """
        Verify a Stripe webhook signature and parse the event.

        Parameters
        ----------
        payload        : Raw request body bytes.
        sig_header     : Value of the ``Stripe-Signature`` HTTP header.
        webhook_secret : Webhook endpoint signing secret (``whsec_…``).

        Returns
        -------
        dict  Parsed Stripe Event object, or error dict.
        """
        if not STRIPE_AVAILABLE:
            return _stripe_unavailable("handle_webhook")

        try:
            event = _stripe_lib.Webhook.construct_event(
                payload, sig_header, webhook_secret
            )
            logger.info("Verified Stripe webhook event: %s", event["type"])
            return dict(event)
        except _stripe_lib.error.SignatureVerificationError as exc:
            logger.warning("Stripe webhook signature verification failed: %s", exc)
            return {"error": "invalid_signature", "message": str(exc)}
        except Exception as exc:  # pragma: no cover
            logger.error("handle_webhook failed: %s", exc)
            return {"error": str(exc)}

    # ------------------------------------------------------------------
    # Customer portal
    # ------------------------------------------------------------------

    def get_customer_portal_url(
        self,
        customer_id: str,
        return_url: str,
    ) -> str:
        """
        Create a Stripe Billing Portal session and return the URL.

        Parameters
        ----------
        customer_id : Stripe customer ID.
        return_url  : URL the customer is sent back to after managing billing.

        Returns
        -------
        str  Portal session URL, or an empty string on error.
        """
        if not STRIPE_AVAILABLE:
            logger.warning("stripe not installed — cannot create portal session")
            return ""

        try:
            portal_session = _stripe_lib.billing_portal.Session.create(
                customer=customer_id,
                return_url=return_url,
            )
            return portal_session["url"]
        except Exception as exc:  # pragma: no cover
            logger.error("get_customer_portal_url failed: %s", exc)
            return ""


# ---------------------------------------------------------------------------
# Module-level singleton
# ---------------------------------------------------------------------------

_billing_instance: Optional[GenesisBilling] = None


def get_billing() -> GenesisBilling:
    """
    Return the module-level GenesisBilling singleton.

    The instance is created on first call and reused thereafter.
    The API key is read from the ``STRIPE_SECRET_KEY`` environment variable.
    """
    global _billing_instance
    if _billing_instance is None:
        _billing_instance = GenesisBilling()
    return _billing_instance
