import logging
from datetime import datetime, timedelta, timezone
from typing import Any, Dict, Optional

from aiogram import Bot
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession

from bot.middlewares.i18n import JsonI18n
from bot.services.user_email_notifications import send_user_notification_email
from config.settings import Settings
from db.dal import payment_dal, subscription_dal, user_dal

from .subscription_service import SubscriptionService


class ReferralService:
    def __init__(
        self,
        settings: Settings,
        subscription_service: SubscriptionService,
        bot: Bot,
        i18n: JsonI18n,
    ):
        self.settings = settings
        self.subscription_service = subscription_service
        self.bot = bot
        self.i18n = i18n

    async def apply_referral_bonuses_for_payment(
        self,
        session: AsyncSession,
        referee_user_id: int,
        purchased_subscription_months: int,
        current_payment_db_id: Optional[int] = None,
        skip_if_active_before_payment: bool = True,
        tariff_key: Optional[str] = None,
    ) -> Dict[str, Any]:

        referee_final_end_date: Optional[datetime] = None
        referee_bonus_applied_days: Optional[int] = None
        inviter_bonus_successfully_applied = False

        try:
            referee_user_model = await user_dal.get_user_by_id(session, referee_user_id)
            if not referee_user_model or referee_user_model.referred_by_id is None:
                logging.debug(
                    f"User {referee_user_id} not referred or inviter ID missing. No referral bonuses."  # noqa: E501
                )
                return {"referee_bonus_applied_days": None, "referee_new_end_date": None}

            # If configured to apply referral bonuses only once per invited user,
            # check if the referee already has succeeded payments.
            # Use getattr with a safe default (True) to avoid AttributeError if
            # running with an older settings schema.
            if getattr(self.settings, "REFERRAL_ONE_BONUS_PER_REFEREE", True):
                try:
                    succeeded_count = await payment_dal.count_user_succeeded_payments(
                        session, referee_user_id, exclude_payment_id=current_payment_db_id
                    )
                    if succeeded_count and succeeded_count > 0:
                        logging.info(
                            f"Referral bonuses skipped for user {referee_user_id}: already has {succeeded_count} succeeded payments."  # noqa: E501
                        )
                        return {"referee_bonus_applied_days": None, "referee_new_end_date": None}
                except Exception as e_cnt:
                    logging.error(
                        f"Failed counting succeeded payments for user {referee_user_id}: {e_cnt}"
                    )

            # Additionally, do not award referral bonuses if the user was active at payment time
            # (has an active subscription now). This avoids giving bonuses to already active users.
            if skip_if_active_before_payment:
                try:
                    if await self.subscription_service.has_active_subscription(
                        session, referee_user_id
                    ):
                        logging.info(
                            f"Referral bonuses skipped for user {referee_user_id}: user currently has an active subscription."  # noqa: E501
                        )
                        return {"referee_bonus_applied_days": None, "referee_new_end_date": None}
                except Exception as e_sub:
                    logging.error(
                        f"Failed to check active subscription for {referee_user_id}: {e_sub}"
                    )

            inviter_user_id = referee_user_model.referred_by_id
            inviter_user_model = await user_dal.get_user_by_id(session, inviter_user_id)

            referee_name_for_msg = referee_user_model.first_name or f"User {referee_user_id}"

            default_lang_for_placeholder = self.settings.DEFAULT_LANGUAGE
            inviter_name_for_referee_msg = (
                inviter_user_model.first_name
                if inviter_user_model and inviter_user_model.first_name
                else self.i18n.gettext(default_lang_for_placeholder, "friend_placeholder")
            )

            inviter_bonus_days, referee_bonus_days = self._referral_bonus_days_for_payment(
                purchased_subscription_months,
                tariff_key=tariff_key,
            )

            if inviter_bonus_days and inviter_bonus_days > 0:
                if not inviter_user_model:
                    logging.warning(
                        f"Inviter user {inviter_user_id} not found in local DB. Cannot apply inviter bonus."  # noqa: E501
                    )
                else:
                    (
                        inviter_panel_uuid,
                        inviter_panel_sub_link_id,
                        _,
                        _,
                    ) = await self.subscription_service._get_or_create_panel_user_link_details(
                        session, inviter_user_id, inviter_user_model
                    )

                    if not inviter_panel_uuid:
                        logging.warning(
                            f"Failed to get/create panel link for inviter {inviter_user_id}. Cannot apply inviter bonus directly to panel."  # noqa: E501
                        )

                    else:
                        new_end_date_inviter = (
                            await self.subscription_service.extend_active_subscription_days(
                                session=session,
                                user_id=inviter_user_id,
                                bonus_days=inviter_bonus_days,
                                reason=f"referral bonus from {referee_name_for_msg}",
                            )
                        )

                        if new_end_date_inviter:
                            inviter_bonus_successfully_applied = True
                            logging.info(
                                f"Bonus of {inviter_bonus_days} days successfully applied/extended for inviter {inviter_user_id}."  # noqa: E501
                            )

                            try:
                                inviter_lang = (
                                    inviter_user_model.language_code or default_lang_for_placeholder
                                )
                                _i = lambda k, **kw: self.i18n.gettext(inviter_lang, k, **kw)
                                message_text = _i(
                                    "referral_bonus_inviter_notification_extended",
                                    days=inviter_bonus_days,
                                    referee_name=referee_name_for_msg,
                                    new_end_date=new_end_date_inviter.strftime("%Y-%m-%d"),
                                )
                                try:
                                    await self.bot.send_message(inviter_user_id, message_text)
                                except Exception as e_notify_inviter:
                                    logging.error(
                                        f"Failed to send bonus notification to inviter {inviter_user_id}: {e_notify_inviter}"  # noqa: E501
                                    )
                                await send_user_notification_email(
                                    settings=self.settings,
                                    i18n=self.i18n,
                                    user=inviter_user_model,
                                    subject_key="email_referral_bonus_subject",
                                    message_text=message_text,
                                    dashboard_url=(
                                        getattr(
                                            self.settings,
                                            "SUBSCRIPTION_MINI_APP_URL",
                                            "",
                                        )
                                        or None
                                    ),
                                )
                            except Exception as e_notify_inviter:
                                logging.error(
                                    f"Failed to prepare bonus notification for inviter {inviter_user_id}: {e_notify_inviter}"  # noqa: E501
                                )
                        else:
                            logging.info(
                                f"Inviter {inviter_user_id} has no active sub to extend. Creating new bonus subscription for {inviter_bonus_days} days."  # noqa: E501
                            )

                            bonus_start_date = datetime.now(timezone.utc)
                            bonus_end_date = bonus_start_date + timedelta(days=inviter_bonus_days)

                            if not inviter_panel_sub_link_id:
                                logging.error(
                                    f"Cannot create bonus subscription for inviter {inviter_user_id}: panel_sub_link_id is missing even after link detail fetch."  # noqa: E501
                                )
                            else:
                                bonus_sub_payload = {
                                    "user_id": inviter_user_id,
                                    "panel_user_uuid": inviter_panel_uuid,
                                    "panel_subscription_uuid": inviter_panel_sub_link_id,
                                    "start_date": bonus_start_date,
                                    "end_date": bonus_end_date,
                                    "duration_months": 0,
                                    "is_active": True,
                                    "status_from_panel": "ACTIVE_BONUS",
                                    "traffic_limit_bytes": self.settings.user_traffic_limit_bytes,
                                    "auto_renew_enabled": False,
                                }
                                try:
                                    await subscription_dal.deactivate_other_active_subscriptions(
                                        session, inviter_panel_uuid, inviter_panel_sub_link_id
                                    )
                                    bonus_sub = await subscription_dal.upsert_subscription(
                                        session, bonus_sub_payload
                                    )

                                    panel_update_success = await self.subscription_service.panel_service.update_user_details_on_panel(  # noqa: E501
                                        inviter_panel_uuid,
                                        {
                                            "expireAt": bonus_end_date.isoformat(
                                                timespec="milliseconds"
                                            ).replace("+00:00", "Z"),
                                            "status": "ACTIVE",
                                        },
                                    )
                                    if panel_update_success:
                                        inviter_bonus_successfully_applied = True
                                        logging.info(
                                            f"New bonus subscription for {inviter_bonus_days} days created for inviter {inviter_user_id}."  # noqa: E501
                                        )

                                        inviter_lang = (
                                            inviter_user_model.language_code
                                            or default_lang_for_placeholder
                                        )
                                        _i = lambda k, **kw: self.i18n.gettext(
                                            inviter_lang, k, **kw
                                        )
                                        message_text = _i(
                                            "referral_bonus_inviter_notification_new_sub",
                                            days=inviter_bonus_days,
                                            referee_name=referee_name_for_msg,
                                            new_end_date=bonus_end_date.strftime("%Y-%m-%d"),
                                        )
                                        try:
                                            await self.bot.send_message(
                                                inviter_user_id,
                                                message_text,
                                            )
                                        except Exception as e_notify_inviter:
                                            logging.error(
                                                f"Failed to send bonus notification to inviter {inviter_user_id}: {e_notify_inviter}"  # noqa: E501
                                            )
                                        await send_user_notification_email(
                                            settings=self.settings,
                                            i18n=self.i18n,
                                            user=inviter_user_model,
                                            subject_key="email_referral_bonus_subject",
                                            message_text=message_text,
                                            dashboard_url=(
                                                getattr(
                                                    self.settings,
                                                    "SUBSCRIPTION_MINI_APP_URL",
                                                    "",
                                                )
                                                or None
                                            ),
                                        )
                                    else:
                                        logging.warning(
                                            f"Failed to update panel for new bonus subscription for inviter {inviter_user_id}. Local bonus sub created (ID: {bonus_sub.subscription_id}) but may not be active on panel."  # noqa: E501
                                        )

                                except Exception as e_create_bonus_sub:
                                    logging.error(
                                        f"Failed to create new bonus subscription for inviter {inviter_user_id}: {e_create_bonus_sub}",  # noqa: E501
                                        exc_info=True,
                                    )

            if referee_bonus_days and referee_bonus_days > 0:
                new_end_date_referee = (
                    await self.subscription_service.extend_active_subscription_days(
                        session=session,
                        user_id=referee_user_id,
                        bonus_days=referee_bonus_days,
                        reason=f"referee bonus (invited by {inviter_name_for_referee_msg})",
                    )
                )
                if new_end_date_referee:
                    referee_final_end_date = new_end_date_referee
                    referee_bonus_applied_days = referee_bonus_days
                    logging.info(
                        f"Bonus of {referee_bonus_days} days successfully applied to referee {referee_user_id}."  # noqa: E501
                    )
                else:
                    logging.warning(
                        f"Failed to apply referee bonus for {referee_user_id} (could not extend their new subscription)."  # noqa: E501
                    )

            return {
                "referee_bonus_applied_days": referee_bonus_applied_days,
                "referee_new_end_date": referee_final_end_date,
                "inviter_bonus_applied_flag": inviter_bonus_successfully_applied,
            }
        except Exception as e:
            logging.error(
                f"Error in apply_referral_bonuses_for_payment for referee {referee_user_id}: {e}",
                exc_info=True,
            )

            raise

    def _referral_bonus_days_for_payment(
        self,
        purchased_subscription_months: int,
        *,
        tariff_key: Optional[str] = None,
    ) -> tuple[Optional[int], Optional[int]]:
        months = int(purchased_subscription_months)
        tariffs_config = getattr(self.settings, "tariffs_config", None)
        if tariff_key and tariffs_config:
            try:
                tariff = tariffs_config.require(str(tariff_key))
            except Exception:
                logging.warning(
                    "Referral bonuses skipped: tariff %s was not found.",
                    tariff_key,
                )
                return None, None
            if tariff.billing_model != "period":
                return None, None
            return (
                tariff.referral_inviter_bonus_days(months),
                tariff.referral_referee_bonus_days(months),
            )

        return (
            self.settings.referral_bonus_inviter.get(months),
            self.settings.referral_bonus_referee.get(months),
        )

    async def generate_referral_link(
        self, session: AsyncSession, bot_username: str, inviter_user_id: int
    ) -> Optional[str]:
        try:
            user = await user_dal.get_user_by_id(session, inviter_user_id)
            if not user:
                logging.warning(
                    "Unable to generate referral link: user %s not found.",
                    inviter_user_id,
                )
                return None

            referral_code = await user_dal.ensure_referral_code(session, user)
            if not referral_code:
                logging.warning(
                    "User %s has no referral code even after regeneration attempt.",
                    inviter_user_id,
                )
                return None

            return f"https://t.me/{bot_username}?start=ref_u{referral_code}"
        except Exception as exc:
            logging.error(
                "Failed to generate referral link for user %s: %s",
                inviter_user_id,
                exc,
                exc_info=True,
            )
            return None

    async def get_referral_stats(self, session: AsyncSession, user_id: int) -> dict:
        """Get referral statistics for a user"""

        try:
            # Count total invited users (referrals)
            invited_count_result = await session.execute(
                text("SELECT COUNT(*) FROM users WHERE referred_by_id = :user_id"),
                {"user_id": user_id},
            )
            invited_count = invited_count_result.scalar() or 0

            # Count users who made successful payments (purchased subscription)
            purchased_count_result = await session.execute(
                text("""
                    SELECT COUNT(DISTINCT u.user_id) 
                    FROM users u 
                    JOIN payments p ON u.user_id = p.user_id 
                    WHERE u.referred_by_id = :user_id 
                    AND p.status = 'succeeded'
                """),
                {"user_id": user_id},
            )
            purchased_count = purchased_count_result.scalar() or 0

            return {"invited_count": invited_count, "purchased_count": purchased_count}
        except Exception as e:
            logging.error(f"Error getting referral stats for user {user_id}: {e}")
            return {"invited_count": 0, "purchased_count": 0}
