import logging
from typing import Any, Callable, Optional, Union
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit

from aiogram import Bot, F, Router, types
from aiogram.filters import Command
from sqlalchemy.ext.asyncio import AsyncSession

from bot.middlewares.i18n import JsonI18n
from bot.services.referral_service import ReferralService
from config.settings import Settings
from db.dal import user_dal

router = Router(name="user_referral_router")


async def referral_command_handler(
    event: Union[types.Message, types.CallbackQuery],
    settings: Settings,
    i18n_data: dict,
    referral_service: ReferralService,
    bot: Bot,
    session: AsyncSession,
    back_callback: str = "main_action:back_to_main",
):
    current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
    i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")

    target_message_obj = event.message if isinstance(event, types.CallbackQuery) else event
    if not target_message_obj:
        logging.error(
            "Target message is None in referral_command_handler (possibly from callback without message)."  # noqa: E501
        )
        if isinstance(event, types.CallbackQuery):
            await event.answer("Error displaying referral info.", show_alert=True)
        return

    if not i18n or not referral_service:
        logging.error("Dependencies (i18n or ReferralService) missing in referral_command_handler")
        await target_message_obj.answer("Service error. Please try again later.")
        if isinstance(event, types.CallbackQuery):
            await event.answer()
        return

    _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)

    try:
        bot_info = await bot.get_me()
        bot_username = bot_info.username
    except Exception as e_bot_info:
        logging.error(f"Failed to get bot info for referral link: {e_bot_info}")
        await target_message_obj.answer(_("error_generating_referral_link"))
        if isinstance(event, types.CallbackQuery):
            await event.answer()
        return

    if not bot_username:
        logging.error("Bot username is None, cannot generate referral link.")
        await target_message_obj.answer(_("error_generating_referral_link"))
        if isinstance(event, types.CallbackQuery):
            await event.answer()
        return

    inviter_user_id = event.from_user.id
    referral_link = await referral_service.generate_referral_link(
        session, bot_username, inviter_user_id
    )

    if not referral_link:
        logging.error(
            "Failed to generate referral link for user %s (probably missing DB record).",
            inviter_user_id,
        )
        await target_message_obj.answer(_("error_generating_referral_link"))
        if isinstance(event, types.CallbackQuery):
            await event.answer()
        return

    if getattr(settings, "traffic_sale_mode", False):
        bonus_details_str = _("referral_not_available_for_traffic")
    else:
        bonus_details_str = _build_referral_bonus_details_text(settings, _, current_lang)

    referral_stats = await referral_service.get_referral_stats(session, inviter_user_id)

    webapp_referral_link = await _generate_webapp_referral_link(
        session,
        settings,
        inviter_user_id,
    )
    webapp_link_section = (
        _(
            "referral_webapp_link_line",
            webapp_referral_link=webapp_referral_link,
        )
        if webapp_referral_link
        else ""
    )

    text = _(
        "referral_program_info_new",
        referral_link=referral_link,
        webapp_link_section=webapp_link_section,
        bonus_details=bonus_details_str,
        invited_count=referral_stats["invited_count"],
        purchased_count=referral_stats["purchased_count"],
    )

    from bot.keyboards.inline.user_keyboards import get_referral_link_keyboard

    reply_markup_val = get_referral_link_keyboard(
        current_lang,
        i18n,
        back_callback=back_callback,
    )

    if isinstance(event, types.Message):
        await event.answer(text, reply_markup=reply_markup_val, disable_web_page_preview=True)
    elif isinstance(event, types.CallbackQuery) and event.message:
        try:
            await event.message.edit_text(
                text, reply_markup=reply_markup_val, disable_web_page_preview=True
            )
        except Exception as e_edit:
            logging.warning(f"Failed to edit message for referral info: {e_edit}. Sending new one.")
            await event.message.answer(
                text, reply_markup=reply_markup_val, disable_web_page_preview=True
            )
        await event.answer()


@router.callback_query(F.data.startswith("referral_action:"))
async def referral_action_handler(
    callback: types.CallbackQuery,
    settings: Settings,
    i18n_data: dict,
    referral_service: ReferralService,
    bot: Bot,
    session: AsyncSession,
):
    action = callback.data.split(":")[1]
    current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
    i18n = i18n_data.get("i18n_instance")
    _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)

    if action == "share_message":
        try:
            bot_info = await bot.get_me()
            bot_username = bot_info.username
            if not bot_username:
                await callback.answer(_("error_generating_referral_link"), show_alert=True)
                return

            inviter_user_id = callback.from_user.id
            referral_link = await referral_service.generate_referral_link(
                session, bot_username, inviter_user_id
            )

            if not referral_link:
                logging.error(
                    "Failed to generate referral link for user %s via inline button.",
                    inviter_user_id,
                )
                await callback.answer(_("error_generating_referral_link"), show_alert=True)
                return

            webapp_referral_link = await _generate_webapp_referral_link(
                session,
                settings,
                inviter_user_id,
            )
            if webapp_referral_link:
                friend_message = _(
                    "referral_friend_message_with_webapp",
                    referral_link=referral_link,
                    webapp_referral_link=webapp_referral_link,
                )
            else:
                friend_message = _("referral_friend_message", referral_link=referral_link)

            await callback.message.answer(friend_message, disable_web_page_preview=True)

        except Exception as e:
            logging.error(f"Error in referral share message: {e}")
            await callback.answer(_("error_occurred_try_again"), show_alert=True)

    await callback.answer()


Translator = Callable[..., str]


def _period_bonus_text(
    translator: Translator,
    *,
    months: int,
    inviter_days: Optional[int],
    referee_days: Optional[int],
) -> str:
    return translator(
        "referral_bonus_per_period",
        months=months,
        inviter_bonus_days=(
            inviter_days if inviter_days is not None else translator("no_bonus_placeholder")
        ),
        referee_bonus_days=(
            referee_days if referee_days is not None else translator("no_bonus_placeholder")
        ),
    )


def _tariff_period_bonus_entries(tariff: Any) -> list[dict[str, Optional[int]]]:
    entries: list[dict[str, Optional[int]]] = []
    for months in sorted(int(month) for month in getattr(tariff, "enabled_periods", [])):
        inviter_days = tariff.referral_inviter_bonus_days(months)
        referee_days = tariff.referral_referee_bonus_days(months)
        if inviter_days is None and referee_days is None:
            continue
        entries.append(
            {
                "months": months,
                "inviter_days": inviter_days,
                "referee_days": referee_days,
            }
        )
    return entries


def _legacy_period_bonus_entries(settings: Settings) -> list[dict[str, Optional[int]]]:
    entries: list[dict[str, Optional[int]]] = []
    for months, _price in sorted(settings.subscription_options.items()):
        inviter_days = settings.referral_bonus_inviter.get(months)
        referee_days = settings.referral_bonus_referee.get(months)
        if inviter_days is None and referee_days is None:
            continue
        entries.append(
            {
                "months": int(months),
                "inviter_days": inviter_days,
                "referee_days": referee_days,
            }
        )
    return entries


def _bonus_days_range(translator: Translator, values: list[int]) -> str:
    return translator(
        "referral_bonus_days_range",
        min_days=min(values),
        max_days=max(values),
    )


def _build_referral_bonus_details_text(
    settings: Settings, translator: Translator, current_lang: str
) -> str:
    tariffs_config = settings.tariffs_config
    if not tariffs_config:
        bonus_info_parts = [
            _period_bonus_text(
                translator,
                months=int(entry["months"] or 0),
                inviter_days=entry["inviter_days"],
                referee_days=entry["referee_days"],
            )
            for entry in _legacy_period_bonus_entries(settings)
        ]
        return (
            "\n".join(bonus_info_parts)
            if bonus_info_parts
            else translator("referral_no_bonuses_configured")
        )

    period_tariffs = [
        tariff for tariff in tariffs_config.enabled_tariffs if tariff.billing_model == "period"
    ]
    if len(period_tariffs) <= 1:
        entries = _tariff_period_bonus_entries(period_tariffs[0]) if period_tariffs else []
        bonus_info_parts = [
            _period_bonus_text(
                translator,
                months=int(entry["months"] or 0),
                inviter_days=entry["inviter_days"],
                referee_days=entry["referee_days"],
            )
            for entry in entries
        ]
        return (
            "\n".join(bonus_info_parts)
            if bonus_info_parts
            else translator("referral_no_bonuses_configured")
        )

    bonus_info_parts = []
    for tariff in period_tariffs:
        entries = _tariff_period_bonus_entries(tariff)
        if not entries:
            continue
        inviter_values = [int(entry["inviter_days"] or 0) for entry in entries]
        referee_values = [int(entry["referee_days"] or 0) for entry in entries]
        bonus_info_parts.append(
            translator(
                "referral_bonus_tariff_range",
                tariff_name=tariff.name(current_lang),
                inviter_bonus_range=_bonus_days_range(translator, inviter_values),
                referee_bonus_range=_bonus_days_range(translator, referee_values),
            )
        )
    return (
        "\n".join(bonus_info_parts)
        if bonus_info_parts
        else translator("referral_no_bonuses_configured")
    )


def _build_webapp_referral_link(
    base_url: Optional[str], referral_code: Optional[str]
) -> Optional[str]:
    if not base_url or not referral_code:
        return None
    parts = urlsplit(base_url)
    query = dict(parse_qsl(parts.query, keep_blank_values=True))
    query["ref"] = f"u{referral_code}"
    return urlunsplit(
        (
            parts.scheme,
            parts.netloc,
            parts.path or "/",
            urlencode(query),
            parts.fragment,
        )
    )


async def _generate_webapp_referral_link(
    session: AsyncSession,
    settings: Settings,
    inviter_user_id: int,
) -> Optional[str]:
    if not settings.SUBSCRIPTION_MINI_APP_URL:
        return None
    db_user = await user_dal.get_user_by_id(session, inviter_user_id)
    referral_code = await user_dal.ensure_referral_code(session, db_user) if db_user else None
    return _build_webapp_referral_link(
        settings.SUBSCRIPTION_MINI_APP_URL,
        referral_code,
    )


@router.message(Command("referral"))
async def referral_command_message_handler(
    message: types.Message,
    settings: Settings,
    i18n_data: dict,
    referral_service: ReferralService,
    bot: Bot,
    session: AsyncSession,
):
    await referral_command_handler(message, settings, i18n_data, referral_service, bot, session)
