import logging

from django.conf import settings
from django.core.exceptions import ValidationError
from django.db.utils import DatabaseError, OperationalError, ProgrammingError

from constants.plans import PlanNames
from util.subscription_helper import (
    get_active_subscription,
    get_customer_subscription_candidate,
    get_stripe_customer,
)
from waffle import get_waffle_switch_model

logger = logging.getLogger(__name__)


PERSONAL_USAGE_REQUIRES_TRIAL_MESSAGE = (
    "Start a free trial to use personal agents or personal API keys."
)


class TrialRequiredValidationError(ValidationError):
    """Signal that personal agent/API access requires starting a trial."""


PERSONAL_FREE_TRIAL_ENFORCEMENT_WAFFLE_SWITCH = "personal_free_trial_enforcement"
# Chat-only recovery path: include incomplete so users with an unfinished checkout
# can get back into chat long enough to resolve billing, without reopening broader
# personal-agent creation or API-key access.
PERSONAL_CHAT_ALLOWED_DELINQUENT_STATUSES = {"past_due", "unpaid", "incomplete"}


def is_personal_trial_enforcement_enabled() -> bool:
    # Keep env-var support as a hard override, while allowing fast runtime flips via Waffle.
    env_enabled = bool(settings.PERSONAL_FREE_TRIAL_ENFORCEMENT_ENABLED)
    if env_enabled:
        return True

    try:
        Switch = get_waffle_switch_model()
        switch = Switch.objects.filter(
            name=PERSONAL_FREE_TRIAL_ENFORCEMENT_WAFFLE_SWITCH,
        ).only("active").first()
    except (DatabaseError, OperationalError, ProgrammingError):
        logger.exception(
            "Failed loading waffle switch '%s' for personal trial enforcement",
            PERSONAL_FREE_TRIAL_ENFORCEMENT_WAFFLE_SWITCH,
        )
        return env_enabled

    if switch is None:
        return env_enabled
    return bool(switch.active)


def is_user_freemium_grandfathered(user) -> bool:
    if not user or not getattr(user, "pk", None):
        return False

    flags = getattr(user, "flags", None)
    if flags is None:
        from api.models import UserFlags

        flags = UserFlags.get_for_user(user)

    return bool(flags and getattr(flags, "is_freemium_grandfathered", False))


def _normalize_subscription_status(value: object) -> str | None:
    if not isinstance(value, str):
        return None
    normalized = value.strip().lower()
    return normalized or None


def _has_delinquent_personal_subscription(user) -> bool:
    customer = get_stripe_customer(user)
    if customer is None:
        return False

    try:
        subscriptions = list(customer.subscriptions.all())
    except (AttributeError, TypeError):
        logger.exception(
            "Failed to inspect personal subscriptions for delinquent chat access user=%s",
            getattr(user, "id", None),
        )
        return False

    candidate_subscription = get_customer_subscription_candidate(user, subscriptions)
    if candidate_subscription is None:
        return False

    stripe_data = getattr(candidate_subscription, "stripe_data", None)
    status = None
    if isinstance(stripe_data, dict):
        status = _normalize_subscription_status(stripe_data.get("status"))
    if status is None:
        status = _normalize_subscription_status(getattr(candidate_subscription, "status", None))
    return status in PERSONAL_CHAT_ALLOWED_DELINQUENT_STATUSES


def can_user_use_personal_agents_and_api(user) -> bool:
    if not user or not getattr(user, "pk", None):
        return False

    if bool(getattr(user, "is_staff", False) or getattr(user, "is_superuser", False)):
        return True

    if not is_personal_trial_enforcement_enabled():
        return True

    if is_user_freemium_grandfathered(user):
        return True

    cache_attr = "_personal_agents_and_api_access_allowed"
    cached = getattr(user, cache_attr, None)
    if cached is not None:
        return bool(cached)

    allowed = False
    try:
        allowed = get_active_subscription(user) is not None
        if not allowed:
            billing = getattr(user, "billing", None)
            if billing and getattr(billing, "subscription", None) != PlanNames.FREE:
                allowed = get_active_subscription(user, sync_with_stripe=True) is not None
    except Exception:
        logger.exception(
            "Failed to resolve active personal subscription for user %s while enforcing free-trial access",
            getattr(user, "id", None),
        )
        billing = getattr(user, "billing", None)
        allowed = bool(billing and getattr(billing, "subscription", None) != PlanNames.FREE)

    setattr(user, cache_attr, bool(allowed))
    return bool(allowed)


def can_user_access_personal_agent_chat(user) -> bool:
    if can_user_use_personal_agents_and_api(user):
        return True

    if not user or not getattr(user, "pk", None):
        return False

    # Let delinquent paid users reach chat so the UI can direct them back to billing.
    cache_attr = "_personal_agent_chat_access_allowed"
    cached = getattr(user, cache_attr, None)
    if cached is not None:
        return bool(cached)

    allowed = _has_delinquent_personal_subscription(user)
    setattr(user, cache_attr, bool(allowed))
    return bool(allowed)
