import csv
import io
import logging
import random
import string
from datetime import datetime, timedelta, timezone
from typing import Optional

from aiogram import F, Router, types
from aiogram.filters import StateFilter
from aiogram.fsm.context import FSMContext
from aiogram.utils.keyboard import InlineKeyboardBuilder, InlineKeyboardButton
from sqlalchemy.ext.asyncio import AsyncSession

from bot.keyboards.inline.admin_keyboards import (
    get_admin_panel_keyboard,
    get_back_to_admin_panel_keyboard,
)
from bot.middlewares.i18n import JsonI18n
from bot.states.admin_states import AdminStates
from config.settings import Settings
from db.dal import promo_code_dal

router = Router(name="promo_bulk_router")


async def create_bulk_promo_prompt_handler(
    callback: types.CallbackQuery,
    state: FSMContext,
    i18n_data: dict,
    settings: Settings,
    session: AsyncSession,
):
    current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
    i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
    if not i18n or not callback.message:
        await callback.answer("Error preparing bulk promo creation.", show_alert=True)
        return
    _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)

    # Step 1: Ask for quantity
    prompt_text = _("admin_bulk_promo_step1_quantity")

    try:
        await callback.message.edit_text(
            prompt_text,
            reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
            parse_mode="HTML",
        )
    except Exception as e:
        logging.warning(f"Could not edit message for bulk promo prompt: {e}. Sending new.")
        await callback.message.answer(
            prompt_text,
            reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
            parse_mode="HTML",
        )
    await callback.answer()
    await state.set_state(AdminStates.waiting_for_bulk_promo_quantity)


def generate_unique_promo_code(length: int = 8) -> str:
    """Generate a unique random promo code"""
    characters = string.ascii_uppercase + string.digits
    return "".join(random.choice(characters) for _ in range(length))


# Step 1: Process quantity
@router.message(AdminStates.waiting_for_bulk_promo_quantity, F.text)
async def process_bulk_promo_quantity_handler(
    message: types.Message, state: FSMContext, i18n_data: dict, settings: Settings
):
    current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
    i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
    if not i18n:
        await message.reply("Language service error.")
        return
    _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)

    try:
        quantity = int(message.text.strip())
        if not (1 <= quantity <= 100):
            await message.answer(_("admin_bulk_promo_invalid_quantity"))
            return

        await state.update_data(quantity=quantity)

        # Step 2: Ask for bonus days
        prompt_text = _("admin_bulk_promo_step2_bonus_days", quantity=quantity)

        await message.answer(
            prompt_text,
            reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
            parse_mode="HTML",
        )
        await state.set_state(AdminStates.waiting_for_bulk_promo_bonus_days)

    except ValueError:
        await message.answer(_("admin_promo_invalid_number"))
    except Exception as e:
        logging.error(f"Error processing bulk promo quantity: {e}")
        await message.answer(_("error_occurred_try_again"))


# Step 2: Process bonus days
@router.message(AdminStates.waiting_for_bulk_promo_bonus_days, F.text)
async def process_bulk_promo_bonus_days_handler(
    message: types.Message, state: FSMContext, i18n_data: dict, settings: Settings
):
    current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
    i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
    if not i18n:
        await message.reply("Language service error.")
        return
    _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)

    try:
        bonus_days = int(message.text.strip())
        if not (1 <= bonus_days <= 365):
            await message.answer(_("admin_promo_invalid_bonus_days"))
            return

        await state.update_data(bonus_days=bonus_days)

        # Step 3: Ask for max activations
        data = await state.get_data()
        prompt_text = _(
            "admin_bulk_promo_step3_max_activations",
            quantity=data.get("quantity"),
            bonus_days=bonus_days,
        )

        await message.answer(
            prompt_text,
            reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
            parse_mode="HTML",
        )
        await state.set_state(AdminStates.waiting_for_bulk_promo_max_activations)

    except ValueError:
        await message.answer(_("admin_promo_invalid_number"))
    except Exception as e:
        logging.error(f"Error processing bulk promo bonus days: {e}")
        await message.answer(_("error_occurred_try_again"))


# Step 3: Process max activations
@router.message(AdminStates.waiting_for_bulk_promo_max_activations, F.text)
async def process_bulk_promo_max_activations_handler(
    message: types.Message, state: FSMContext, i18n_data: dict, settings: Settings
):
    current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
    i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
    if not i18n:
        await message.reply("Language service error.")
        return
    _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)

    try:
        max_activations = int(message.text.strip())
        if not (1 <= max_activations <= 10000):
            await message.answer(_("admin_promo_invalid_max_activations"))
            return

        await state.update_data(max_activations=max_activations)

        # Step 4: Ask for validity
        data = await state.get_data()
        prompt_text = _(
            "admin_bulk_promo_step4_validity",
            quantity=data.get("quantity"),
            bonus_days=data.get("bonus_days"),
            max_activations=max_activations,
        )

        # Create keyboard for validity options
        builder = InlineKeyboardBuilder()
        builder.row(
            InlineKeyboardButton(
                text=_("admin_promo_unlimited_validity"),
                callback_data="bulk_promo_unlimited_validity",
            )
        )
        builder.row(
            InlineKeyboardButton(
                text=_("admin_promo_set_validity_days"), callback_data="bulk_promo_set_validity"
            )
        )
        builder.row(
            InlineKeyboardButton(text=_("admin_back_to_panel"), callback_data="admin_action:main")
        )

        await message.answer(prompt_text, reply_markup=builder.as_markup(), parse_mode="HTML")
        await state.set_state(AdminStates.waiting_for_bulk_promo_validity_days)

    except ValueError:
        await message.answer(_("admin_promo_invalid_number"))
    except Exception as e:
        logging.error(f"Error processing bulk promo max activations: {e}")
        await message.answer(_("error_occurred_try_again"))


# Step 4: Handle unlimited validity
@router.callback_query(
    F.data == "bulk_promo_unlimited_validity",
    StateFilter(AdminStates.waiting_for_bulk_promo_validity_days),
)
async def process_bulk_promo_unlimited_validity(
    callback: types.CallbackQuery,
    state: FSMContext,
    i18n_data: dict,
    settings: Settings,
    session: AsyncSession,
):
    await state.update_data(validity_days=None)
    await create_bulk_promo_codes_final(callback, state, i18n_data, settings, session)


# Step 4: Handle set validity
@router.callback_query(
    F.data == "bulk_promo_set_validity",
    StateFilter(AdminStates.waiting_for_bulk_promo_validity_days),
)
async def process_bulk_promo_set_validity(
    callback: types.CallbackQuery, state: FSMContext, i18n_data: dict, settings: Settings
):
    current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
    i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
    if not i18n or not callback.message:
        await callback.answer("Error processing validity.", show_alert=True)
        return
    _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)

    data = await state.get_data()
    prompt_text = _(
        "admin_bulk_promo_enter_validity_days",
        quantity=data.get("quantity"),
        bonus_days=data.get("bonus_days"),
        max_activations=data.get("max_activations"),
    )

    try:
        await callback.message.edit_text(
            prompt_text,
            reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
            parse_mode="HTML",
        )
    except Exception:
        await callback.message.answer(
            prompt_text,
            reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
            parse_mode="HTML",
        )
    await callback.answer()


# Step 4: Process validity days
@router.message(AdminStates.waiting_for_bulk_promo_validity_days, F.text)
async def process_bulk_promo_validity_days_handler(
    message: types.Message,
    state: FSMContext,
    i18n_data: dict,
    settings: Settings,
    session: AsyncSession,
):
    current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
    i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
    if not i18n:
        await message.reply("Language service error.")
        return
    _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)

    try:
        validity_days = int(message.text.strip())
        if not (1 <= validity_days <= 365):
            await message.answer(_("admin_promo_invalid_validity_days"))
            return

        await state.update_data(validity_days=validity_days)
        await create_bulk_promo_codes_final(message, state, i18n_data, settings, session)

    except ValueError:
        await message.answer(_("admin_promo_invalid_number"))
    except Exception as e:
        logging.error(f"Error processing bulk promo validity days: {e}")
        await message.answer(_("error_occurred_try_again"))


async def create_bulk_promo_codes_final(
    callback_or_message,
    state: FSMContext,
    i18n_data: dict,
    settings: Settings,
    session: AsyncSession,
):
    """Final step - create multiple promo codes in database"""
    current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
    i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
    if not i18n:
        return
    _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)

    try:
        data = await state.get_data()
        quantity = data["quantity"]

        # Show progress message
        progress_text = _("admin_bulk_promo_creating", quantity=quantity)

        if hasattr(callback_or_message, "message"):  # CallbackQuery
            try:
                await callback_or_message.message.edit_text(progress_text, parse_mode="HTML")
            except Exception:
                await callback_or_message.message.answer(progress_text, parse_mode="HTML")
            await callback_or_message.answer()
        else:  # Message
            await callback_or_message.answer(progress_text, parse_mode="HTML")

        # Generate and create promo codes
        created_codes = []
        failed_codes = []

        for i in range(quantity):
            try:
                # Generate unique code
                attempts = 0
                while attempts < 10:  # Max 10 attempts to generate unique code
                    promo_code = generate_unique_promo_code()
                    existing_promo = await promo_code_dal.get_promo_code_by_code(
                        session, promo_code
                    )
                    if not existing_promo:
                        break
                    attempts += 1

                if attempts >= 10:
                    failed_codes.append(f"Код #{i + 1} (не удалось сгенерировать уникальный)")
                    continue

                # Prepare promo code data
                promo_data = {
                    "code": promo_code,
                    "bonus_days": data["bonus_days"],
                    "max_activations": data["max_activations"],
                    "current_activations": 0,
                    "is_active": True,
                    "created_by_admin_id": callback_or_message.from_user.id,
                    "created_at": datetime.now(timezone.utc),
                }

                # Set validity
                if data.get("validity_days"):
                    promo_data["valid_until"] = datetime.now(timezone.utc) + timedelta(
                        days=data["validity_days"]
                    )
                else:
                    promo_data["valid_until"] = None

                # Create promo code
                created_promo = await promo_code_dal.create_promo_code(session, promo_data)
                created_codes.append(created_promo.code)

            except Exception as e:
                logging.error(f"Error creating bulk promo code #{i + 1}: {e}")
                failed_codes.append(f"Код #{i + 1} ({str(e)[:50]})")

        await session.commit()

        # Success message
        success_lines = [
            _("admin_bulk_promo_created_title"),
            _("admin_bulk_promo_created_stats", created=len(created_codes), total=quantity),
        ]

        if data.get("validity_days"):
            validity_text = f"{data['validity_days']} дней"
        else:
            validity_text = _("admin_promo_unlimited")

        success_lines.append(
            _(
                "admin_bulk_promo_settings",
                bonus_days=data["bonus_days"],
                max_activations=data["max_activations"],
                validity=validity_text,
            )
        )

        # Create CSV file with promo codes if any were created
        csv_file = None
        if created_codes:
            success_lines.append(f"\n🎟 <b>Создано {len(created_codes)} промокодов</b>")
            success_lines.append("📄 CSV файл с промокодами отправлен отдельным сообщением")

            # Create CSV file
            output = io.StringIO()
            writer = csv.writer(output)

            # CSV headers
            writer.writerow(
                [
                    "Промокод",
                    "Бонусные дни",
                    "Макс. активации",
                    "Действителен до",
                    "Команда для старта",
                    "Ссылка для активации",
                ]
            )

            # Get real bot username
            bot_username = "your_bot"  # fallback
            try:
                if hasattr(callback_or_message, "message"):
                    bot = callback_or_message.message.bot
                else:
                    bot = callback_or_message.bot

                bot_info = await bot.get_me()
                bot_username = bot_info.username or "your_bot"
            except Exception as e:
                logging.error(f"Failed to get bot username for CSV links: {e}")
                bot_username = "your_bot"

            for code in created_codes:
                # Determine validity info
                if data.get("validity_days"):
                    valid_until = (
                        datetime.now(timezone.utc) + timedelta(days=data["validity_days"])
                    ).strftime("%Y-%m-%d %H:%M:%S")
                else:
                    valid_until = "Без ограничений"

                start_command = f"/start promo_{code}"
                telegram_link = f"https://t.me/{bot_username}?start=promo_{code}"

                writer.writerow(
                    [
                        code,
                        data["bonus_days"],
                        data["max_activations"],
                        valid_until,
                        start_command,
                        telegram_link,
                    ]
                )

            output.seek(0)

            # Create file for sending
            filename = f"bulk_promo_codes_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
            csv_file = types.BufferedInputFile(
                output.getvalue().encode("utf-8-sig"),  # BOM for correct Excel display
                filename=filename,
            )

        if failed_codes:
            success_lines.append(f"\n❌ <b>Ошибки ({len(failed_codes)}):</b>")
            for error in failed_codes[:5]:  # Show first 5 errors
                success_lines.append(error)
            if len(failed_codes) > 5:
                success_lines.append(f"... и еще {len(failed_codes) - 5} ошибок")

        success_text = "\n".join(success_lines)

        if hasattr(callback_or_message, "message"):  # CallbackQuery
            try:
                await callback_or_message.message.edit_text(
                    success_text,
                    reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
                    parse_mode="HTML",
                )
                message_obj = callback_or_message.message
            except Exception:
                message_obj = await callback_or_message.message.answer(
                    success_text,
                    reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
                    parse_mode="HTML",
                )
            await callback_or_message.answer()
        else:  # Message
            message_obj = await callback_or_message.answer(
                success_text,
                reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
                parse_mode="HTML",
            )

        # Send CSV file if created
        if csv_file:
            csv_caption = f"📄 Промокоды для массового создания\n💫 Всего: {len(created_codes)} промокодов\n🎁 Бонус: {data['bonus_days']} дней каждый"  # noqa: E501
            await message_obj.answer_document(csv_file, caption=csv_caption)

        await state.clear()

    except Exception as e:
        logging.error(f"Error creating bulk promo codes: {e}")
        error_text = _("error_occurred_try_again")

        if hasattr(callback_or_message, "message"):  # CallbackQuery
            await callback_or_message.message.answer(error_text)
        else:  # Message
            await callback_or_message.answer(error_text)

        await state.clear()


# Cancel bulk promo creation
@router.callback_query(
    F.data == "admin_action:main",
    StateFilter(
        AdminStates.waiting_for_bulk_promo_quantity,
        AdminStates.waiting_for_bulk_promo_bonus_days,
        AdminStates.waiting_for_bulk_promo_max_activations,
        AdminStates.waiting_for_bulk_promo_validity_days,
    ),
)
async def cancel_bulk_promo_creation_state_to_menu(
    callback: types.CallbackQuery,
    state: FSMContext,
    settings: Settings,
    i18n_data: dict,
    session: AsyncSession,
):
    current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
    i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
    if not i18n or not callback.message:
        await callback.answer("Error cancelling.", show_alert=True)
        return
    _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)

    try:
        await callback.message.edit_text(
            _(key="admin_panel_title"),
            reply_markup=get_admin_panel_keyboard(i18n, current_lang, settings),
        )
    except Exception:
        await callback.message.answer(
            _(key="admin_panel_title"),
            reply_markup=get_admin_panel_keyboard(i18n, current_lang, settings),
        )

    await callback.answer(_("admin_bulk_promo_creation_cancelled"))
    await state.clear()
