"""
Stripe Client Module

Provides a typed wrapper around the Stripe Python SDK.
All interactions with Stripe should go through this client.
"""

from __future__ import annotations

import os
from dataclasses import dataclass
from functools import lru_cache
from typing import TYPE_CHECKING, Any

import stripe

if TYPE_CHECKING:
    from stripe import (
        Customer,
        Price,
        Product,
        Subscription,
    )


@dataclass
class StripeConfig:
    """Configuration for Stripe client."""

    api_key: str
    webhook_secret: str
    api_version: str = "2024-12-18.acacia"
    live_mode: bool = False

    @classmethod
    def from_env(cls) -> StripeConfig:
        """Create config from environment variables."""
        live_mode = os.getenv("STRIPE_LIVE_MODE", "false").lower() == "true"

        if live_mode:
            api_key = os.getenv("STRIPE_LIVE_SECRET_KEY", "")
        else:
            api_key = os.getenv("STRIPE_TEST_SECRET_KEY", "")

        webhook_secret = os.getenv("DJSTRIPE_WEBHOOK_SECRET", "")
        api_version = os.getenv("STRIPE_API_VERSION", "2024-12-18.acacia")

        return cls(
            api_key=api_key,
            webhook_secret=webhook_secret,
            api_version=api_version,
            live_mode=live_mode,
        )


class StripeClient:
    """
    Typed wrapper around Stripe SDK.

    Provides a clean interface for common Stripe operations with
    proper type hints and error handling.
    """

    def __init__(self, config: StripeConfig | None = None):
        """
        Initialize the Stripe client.

        Args:
            config: Stripe configuration. If None, loads from environment.
        """
        self.config = config or StripeConfig.from_env()

        # Configure the stripe module
        stripe.api_key = self.config.api_key
        stripe.api_version = self.config.api_version

    @property
    def is_configured(self) -> bool:
        """Check if Stripe is properly configured."""
        return bool(self.config.api_key)

    @property
    def is_live(self) -> bool:
        """Check if using live mode."""
        return self.config.live_mode

    # =========================================================================
    # Customer Operations
    # =========================================================================

    def create_customer(
        self,
        email: str,
        name: str | None = None,
        metadata: dict[str, str] | None = None,
    ) -> Customer:
        """
        Create a new Stripe customer.

        Args:
            email: Customer email address
            name: Customer name
            metadata: Additional metadata to store

        Returns:
            Created Customer object
        """
        params: dict[str, Any] = {"email": email}
        if name:
            params["name"] = name
        if metadata:
            params["metadata"] = metadata

        return stripe.Customer.create(**params)

    def get_customer(self, customer_id: str) -> Customer | None:
        """
        Retrieve a customer by ID.

        Args:
            customer_id: Stripe customer ID (cus_xxx)

        Returns:
            Customer object or None if not found
        """
        try:
            return stripe.Customer.retrieve(customer_id)
        except stripe.error.InvalidRequestError:
            return None

    def update_customer(
        self,
        customer_id: str,
        email: str | None = None,
        name: str | None = None,
        metadata: dict[str, str] | None = None,
    ) -> Customer:
        """
        Update an existing customer.

        Args:
            customer_id: Stripe customer ID
            email: New email address
            name: New name
            metadata: Metadata to update (merged with existing)

        Returns:
            Updated Customer object
        """
        params: dict[str, Any] = {}
        if email:
            params["email"] = email
        if name:
            params["name"] = name
        if metadata:
            params["metadata"] = metadata

        return stripe.Customer.modify(customer_id, **params)

    # =========================================================================
    # Subscription Operations
    # =========================================================================

    def create_subscription(
        self,
        customer_id: str,
        price_id: str,
        quantity: int = 1,
        metadata: dict[str, str] | None = None,
        trial_period_days: int | None = None,
    ) -> Subscription:
        """
        Create a new subscription.

        Args:
            customer_id: Stripe customer ID
            price_id: Stripe price ID
            quantity: Number of units
            metadata: Additional metadata
            trial_period_days: Number of trial days

        Returns:
            Created Subscription object
        """
        params: dict[str, Any] = {
            "customer": customer_id,
            "items": [{"price": price_id, "quantity": quantity}],
        }
        if metadata:
            params["metadata"] = metadata
        if trial_period_days is not None:
            params["trial_period_days"] = trial_period_days

        return stripe.Subscription.create(**params)

    def get_subscription(self, subscription_id: str) -> Subscription | None:
        """
        Retrieve a subscription by ID.

        Args:
            subscription_id: Stripe subscription ID (sub_xxx)

        Returns:
            Subscription object or None if not found
        """
        try:
            return stripe.Subscription.retrieve(subscription_id)
        except stripe.error.InvalidRequestError:
            return None

    def cancel_subscription(
        self,
        subscription_id: str,
        at_period_end: bool = True,
    ) -> Subscription:
        """
        Cancel a subscription.

        Args:
            subscription_id: Stripe subscription ID
            at_period_end: If True, cancel at end of billing period

        Returns:
            Updated Subscription object
        """
        if at_period_end:
            return stripe.Subscription.modify(
                subscription_id,
                cancel_at_period_end=True,
            )
        else:
            return stripe.Subscription.cancel(subscription_id)

    def update_subscription_quantity(
        self,
        subscription_id: str,
        quantity: int,
    ) -> Subscription:
        """
        Update the quantity of a subscription.

        Args:
            subscription_id: Stripe subscription ID
            quantity: New quantity

        Returns:
            Updated Subscription object
        """
        subscription = stripe.Subscription.retrieve(subscription_id)
        if subscription.items and subscription.items.data:
            item_id = subscription.items.data[0].id
            return stripe.Subscription.modify(
                subscription_id,
                items=[{"id": item_id, "quantity": quantity}],
            )
        raise ValueError("Subscription has no items")

    # =========================================================================
    # Product/Price Operations
    # =========================================================================

    def get_product(self, product_id: str) -> Product | None:
        """
        Retrieve a product by ID.

        Args:
            product_id: Stripe product ID (prod_xxx)

        Returns:
            Product object or None if not found
        """
        try:
            return stripe.Product.retrieve(product_id)
        except stripe.error.InvalidRequestError:
            return None

    def get_price(self, price_id: str) -> Price | None:
        """
        Retrieve a price by ID.

        Args:
            price_id: Stripe price ID (price_xxx)

        Returns:
            Price object or None if not found
        """
        try:
            return stripe.Price.retrieve(price_id)
        except stripe.error.InvalidRequestError:
            return None

    def list_prices(
        self,
        product_id: str | None = None,
        active: bool | None = True,
        limit: int = 100,
    ) -> list[Price]:
        """
        List prices, optionally filtered by product.

        Args:
            product_id: Filter by product ID
            active: Filter by active status
            limit: Maximum number of results

        Returns:
            List of Price objects
        """
        params: dict[str, Any] = {"limit": limit}
        if product_id:
            params["product"] = product_id
        if active is not None:
            params["active"] = active

        return list(stripe.Price.list(**params))

    # =========================================================================
    # Checkout Operations
    # =========================================================================

    def create_checkout_session(
        self,
        price_id: str,
        success_url: str,
        cancel_url: str,
        customer_id: str | None = None,
        customer_email: str | None = None,
        mode: str = "subscription",
        quantity: int = 1,
        metadata: dict[str, str] | None = None,
        allow_promotion_codes: bool = True,
    ) -> stripe.checkout.Session:
        """
        Create a Stripe Checkout session.

        Args:
            price_id: Stripe price ID
            success_url: URL to redirect on success
            cancel_url: URL to redirect on cancel
            customer_id: Existing customer ID
            customer_email: Customer email (for new customers)
            mode: "subscription" or "payment"
            quantity: Number of units
            metadata: Additional metadata
            allow_promotion_codes: Allow promo codes

        Returns:
            Checkout Session object
        """
        params: dict[str, Any] = {
            "mode": mode,
            "success_url": success_url,
            "cancel_url": cancel_url,
            "line_items": [{"price": price_id, "quantity": quantity}],
            "allow_promotion_codes": allow_promotion_codes,
        }

        if customer_id:
            params["customer"] = customer_id
        elif customer_email:
            params["customer_email"] = customer_email

        if metadata:
            params["metadata"] = metadata

        return stripe.checkout.Session.create(**params)

    # =========================================================================
    # Billing Portal
    # =========================================================================

    def create_portal_session(
        self,
        customer_id: str,
        return_url: str,
    ) -> stripe.billing_portal.Session:
        """
        Create a billing portal session for customer self-service.

        Args:
            customer_id: Stripe customer ID
            return_url: URL to return to after portal

        Returns:
            Billing Portal Session object
        """
        return stripe.billing_portal.Session.create(
            customer=customer_id,
            return_url=return_url,
        )

    # =========================================================================
    # Webhook Verification
    # =========================================================================

    def verify_webhook(
        self,
        payload: bytes,
        signature: str,
    ) -> dict[str, Any]:
        """
        Verify a webhook signature and return the event data.

        Args:
            payload: Raw request body
            signature: Stripe-Signature header value

        Returns:
            Event data dictionary

        Raises:
            SignatureVerificationError: If signature is invalid
        """
        event = stripe.Webhook.construct_event(
            payload,
            signature,
            self.config.webhook_secret,
        )
        return dict(event)


@lru_cache(maxsize=1)
def get_stripe_client() -> StripeClient:
    """
    Get a cached Stripe client instance.

    Returns:
        Configured StripeClient
    """
    return StripeClient()


def verify_webhook_signature(
    payload: bytes,
    signature: str,
    secret: str | None = None,
) -> dict[str, Any]:
    """
    Verify a Stripe webhook signature.

    This is a standalone function for use in webhook handlers
    that don't need the full client.

    Args:
        payload: Raw request body
        signature: Stripe-Signature header value
        secret: Webhook secret (uses env var if not provided)

    Returns:
        Event data dictionary

    Raises:
        SignatureVerificationError: If signature is invalid
    """
    if secret is None:
        secret = os.getenv("DJSTRIPE_WEBHOOK_SECRET", "")

    event = stripe.Webhook.construct_event(payload, signature, secret)
    return dict(event)
