"""Subscription lifecycle management via Stripe."""

from __future__ import annotations

from datetime import UTC, datetime

import stripe

from billing.config import get_config
from billing.models import Subscription


def _get_customer_id(user_id: str) -> str:
    """Look up the Stripe customer ID by user_id stored in metadata.

    Args:
        user_id: Internal user identifier.

    Returns:
        Stripe customer ID.

    Raises:
        ValueError: If no customer is found for the given user_id.
    """
    customers = stripe.Customer.search(
        query=f'metadata["user_id"]:"{user_id}"',
        limit=1,
    )

    if not customers.data:
        raise ValueError(f"No Stripe customer found for user_id '{user_id}'")

    return customers.data[0].id


def _resolve_plan_name(price_id: str) -> str:
    """Resolve a Stripe price_id back to a plan name from config.

    Returns the price_id itself if no matching plan is found.
    """
    config = get_config()
    for plan_name, configured_price_id in config.product_plans.items():
        if configured_price_id == price_id:
            return plan_name
    return price_id


def _stripe_sub_to_model(sub: stripe.Subscription, user_id: str) -> Subscription:
    """Convert a Stripe Subscription object to our domain model."""
    price_id = sub["items"]["data"][0]["price"]["id"]
    return Subscription(
        id=sub.id,
        user_id=user_id,
        plan_name=_resolve_plan_name(price_id),
        status=sub.status,
        current_period_start=datetime.fromtimestamp(sub.current_period_start, tz=UTC),
        current_period_end=datetime.fromtimestamp(sub.current_period_end, tz=UTC),
        cancel_at_period_end=sub.cancel_at_period_end,
    )


def get_subscription(user_id: str) -> Subscription | None:
    """Fetch the current active subscription for a user.

    Args:
        user_id: Internal user identifier.

    Returns:
        Subscription if found, None if the user has no active subscription.

    Raises:
        stripe.StripeError: On Stripe API failures.
    """
    try:
        customer_id = _get_customer_id(user_id)
    except ValueError:
        return None

    subscriptions = stripe.Subscription.list(
        customer=customer_id,
        status="active",
        limit=1,
        expand=["data.items.data.price"],
    )

    if not subscriptions.data:
        # Also check for past_due or trialing
        subscriptions = stripe.Subscription.list(
            customer=customer_id,
            limit=1,
            expand=["data.items.data.price"],
        )
        if not subscriptions.data:
            return None

    return _stripe_sub_to_model(subscriptions.data[0], user_id)


def cancel_subscription(user_id: str, at_period_end: bool = True) -> Subscription:
    """Cancel a user's subscription.

    Args:
        user_id: Internal user identifier.
        at_period_end: If True, cancel at the end of the current billing period.
            If False, cancel immediately.

    Returns:
        Updated Subscription after cancellation.

    Raises:
        ValueError: If no active subscription is found.
        stripe.StripeError: On Stripe API failures.
    """
    sub = get_subscription(user_id)
    if sub is None:
        raise ValueError(f"No active subscription found for user_id '{user_id}'")

    if at_period_end:
        updated = stripe.Subscription.modify(
            sub.id,
            cancel_at_period_end=True,
        )
    else:
        updated = stripe.Subscription.cancel(sub.id)

    return _stripe_sub_to_model(updated, user_id)


def update_subscription(user_id: str, new_plan: str) -> Subscription:
    """Change a user's subscription to a different plan (proration applied).

    Args:
        user_id: Internal user identifier.
        new_plan: New plan name as defined in BillingConfig.product_plans.

    Returns:
        Updated Subscription with the new plan.

    Raises:
        ValueError: If no active subscription or unknown plan.
        stripe.StripeError: On Stripe API failures.
    """
    config = get_config()
    new_price_id = config.product_plans.get(new_plan)
    if not new_price_id:
        available = ", ".join(config.product_plans.keys())
        raise ValueError(f"Unknown plan '{new_plan}'. Available plans: {available}")

    sub = get_subscription(user_id)
    if sub is None:
        raise ValueError(f"No active subscription found for user_id '{user_id}'")

    # Get the current subscription item to replace
    stripe_sub = stripe.Subscription.retrieve(sub.id, expand=["items.data.price"])
    current_item_id = stripe_sub["items"]["data"][0].id

    updated = stripe.Subscription.modify(
        sub.id,
        items=[
            {
                "id": current_item_id,
                "price": new_price_id,
            }
        ],
        proration_behavior="create_prorations",
    )

    return _stripe_sub_to_model(updated, user_id)
