"""Generic purchase handler.

Screens are driven by the flow definition returned by the backend, so adding a
product (or a whole new flow) never requires touching these handlers.
"""
import logging
from typing import Any

from aiogram import F, Router
from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery, Message

from bot.keyboards.common import main_menu_keyboard
from bot.keyboards.purchase import (
    categories_keyboard,
    comment_type_keyboard,
    orders_keyboard,
    products_keyboard,
    step_keyboard,
)
from bot.services.api import ApiError, api
from bot.services.checkout import render, show_invoice, show_payment_methods, show_result
from bot.services.texts import texts
from bot.states.purchase import PurchaseStates, TopupStates

logger = logging.getLogger("bot.purchase")
router = Router(name="purchase")


# -- catalog --------------------------------------------------------
@router.callback_query(F.data == "menu:buy")
async def open_buy_menu(callback: CallbackQuery, state: FSMContext) -> None:
    await state.clear()
    categories = await api.categories()
    await render(callback, await texts.text("buy_menu"), await categories_keyboard(categories))
    await callback.answer()


@router.callback_query(F.data.startswith("cat:"))
async def open_category(callback: CallbackQuery, state: FSMContext) -> None:
    category_id = callback.data.split(":", 1)[1]
    products = await api.products(category_id)
    if not products:
        await callback.answer(await texts.text("no_products"), show_alert=True)
        return

    await state.update_data(category_id=category_id)
    await render(callback, await texts.text("choose_product"), await products_keyboard(products))
    await callback.answer()


@router.callback_query(F.data.startswith("prod:"))
async def select_product(callback: CallbackQuery, state: FSMContext) -> None:
    product_id = callback.data.split(":", 1)[1]
    product = await api.product(product_id)
    definition = await api.flow(product["flow"], product_id)

    await state.set_state(PurchaseStates.collecting)
    await state.update_data(
        product_id=product_id,
        flow=product["flow"],
        steps=definition["steps"],
        options=definition["options"],
        step_index=0,
        inputs={},
        order_id=None,
    )
    await callback.answer()
    await _ask_step(callback, state)


# -- step engine ----------------------------------------------------
async def _ask_step(event: Message | CallbackQuery, state: FSMContext) -> None:
    data = await state.get_data()
    steps: list[dict[str, Any]] = data.get("steps", [])
    index: int = data.get("step_index", 0)

    if index >= len(steps):
        await _create_order(event, state)
        return

    step = steps[index]
    prompt = await texts.text(step["prompt"], **_prompt_args(step))
    await render(event, prompt, await step_keyboard(step, can_go_back=True))


def _prompt_args(step: dict[str, Any]) -> dict[str, Any]:
    config = step.get("config", {})
    return {
        "min": config.get("min", ""),
        "max": config.get("max", ""),
        "max_length": config.get("max_length", 200),
    }


async def _advance(event: Message | CallbackQuery, state: FSMContext, value: Any = None) -> None:
    data = await state.get_data()
    steps = data.get("steps", [])
    index = data.get("step_index", 0)
    inputs = dict(data.get("inputs", {}))

    if index < len(steps) and value is not None:
        inputs[steps[index]["key"]] = value

    await state.update_data(inputs=inputs, step_index=index + 1)
    await _ask_step(event, state)


@router.message(PurchaseStates.collecting)
async def collect_input(message: Message, state: FSMContext) -> None:
    if not message.text:
        await message.answer(await texts.error("generic"))
        return
    await _advance(message, state, message.text.strip())


@router.callback_query(F.data == "step:self", PurchaseStates.collecting)
async def use_own_username(callback: CallbackQuery, state: FSMContext) -> None:
    username = callback.from_user.username if callback.from_user else None
    if not username:
        await callback.answer(await texts.error("invalid_username"), show_alert=True)
        return
    await callback.answer()
    await _advance(callback, state, username)


@router.callback_query(F.data == "step:balance", PurchaseStates.collecting)
async def use_wallet_balance(callback: CallbackQuery, state: FSMContext) -> None:
    """"محاسبه با موجودی من" — the backend works out the affordable maximum."""
    data = await state.get_data()
    try:
        result = await api.max_affordable(
            callback.from_user.id, data.get("product_id"), data.get("flow")
        )
    except ApiError as exc:
        await callback.answer(await texts.error(exc.code), show_alert=True)
        return

    await callback.answer()
    await _advance(callback, state, result["value"])


@router.callback_query(F.data == "step:skip", PurchaseStates.collecting)
async def skip_step(callback: CallbackQuery, state: FSMContext) -> None:
    await callback.answer()
    await _advance(callback, state, None)


@router.callback_query(F.data == "step:back", PurchaseStates.collecting)
async def step_back(callback: CallbackQuery, state: FSMContext) -> None:
    data = await state.get_data()
    index = data.get("step_index", 0)

    if index <= 0:
        category_id = data.get("category_id")
        await callback.answer()
        if category_id:
            products = await api.products(category_id)
            await render(
                callback, await texts.text("choose_product"), await products_keyboard(products)
            )
        else:
            await open_buy_menu(callback, state)
        return

    await state.update_data(step_index=index - 1)
    await callback.answer()
    await _ask_step(callback, state)


# -- draft order & invoice ------------------------------------------
async def _create_order(event: Message | CallbackQuery, state: FSMContext) -> None:
    data = await state.get_data()
    user = event.from_user
    if not user:
        return

    payload = {
        "product_id": data.get("product_id"),
        "flow": data.get("flow"),
        "input_data": data.get("inputs", {}),
    }
    try:
        order = await api.create_order(user.id, payload)
    except ApiError as exc:
        await _handle_step_error(event, state, exc)
        return

    await state.update_data(order_id=order["id"], inputs=order.get("input_data", {}))
    await state.set_state(None)
    await show_invoice(event, order, data.get("options", []))


async def _handle_step_error(
    event: Message | CallbackQuery, state: FSMContext, exc: ApiError
) -> None:
    """Backend validation failed: explain and ask the same step again."""
    data = await state.get_data()
    index = max(int(data.get("step_index", 1)) - 1, 0)
    await state.update_data(step_index=index)
    await state.set_state(PurchaseStates.collecting)

    message = await texts.error(exc.code)
    if isinstance(event, CallbackQuery):
        await event.answer(message, show_alert=True)
    elif event:
        await event.answer(message)
    await _ask_step(event, state)


async def _show_order(event: CallbackQuery, state: FSMContext, order_id: str) -> None:
    order = await api.order(event.from_user.id, order_id)
    options = (await state.get_data()).get("options")
    if options is None:
        definition = await api.flow(order["product_snapshot"].get("flow", ""), order.get("product_id"))
        options = definition["options"]
        await state.update_data(options=options)
    await show_invoice(event, order, options)


@router.callback_query(F.data.startswith("ord:invoice:"))
async def show_order_invoice(callback: CallbackQuery, state: FSMContext) -> None:
    await state.set_state(None)
    await _show_order(callback, state, callback.data.split(":")[2])
    await callback.answer()


@router.callback_query(F.data.startswith("ord:view:"))
async def view_order(callback: CallbackQuery, state: FSMContext) -> None:
    order = await api.order(callback.from_user.id, callback.data.split(":")[2])
    if order["status"] in ("draft", "waiting_payment"):
        await _show_order(callback, state, order["id"])
    else:
        await show_result(callback, order)
    await callback.answer()


@router.callback_query(F.data.startswith("ord:confirm:"))
async def confirm_order(callback: CallbackQuery, state: FSMContext) -> None:
    order_id = callback.data.split(":")[2]
    result = await api.confirm_order(callback.from_user.id, order_id)
    await state.update_data(order_id=order_id)
    await show_payment_methods(callback, result["order"], result["payment"])
    await callback.answer()


@router.callback_query(F.data.startswith("ord:pay:"))
async def back_to_payment(callback: CallbackQuery, state: FSMContext) -> None:
    order_id = callback.data.split(":")[2]
    result = await api.confirm_order(callback.from_user.id, order_id)
    await state.set_state(None)
    await show_payment_methods(callback, result["order"], result["payment"])
    await callback.answer()


@router.callback_query(F.data.startswith("ord:cancel:"))
async def cancel_order(callback: CallbackQuery, state: FSMContext) -> None:
    order_id = callback.data.split(":")[2]
    try:
        await api.cancel_order(callback.from_user.id, order_id)
    except ApiError as exc:
        logger.warning("cancel failed: %s", exc.code)
    await state.clear()
    await render(callback, await texts.text("payment_cancelled"), await main_menu_keyboard())
    await callback.answer()


# -- coupon ---------------------------------------------------------
@router.callback_query(F.data.startswith("ord:coupon:"))
async def ask_coupon(callback: CallbackQuery, state: FSMContext) -> None:
    from bot.keyboards.common import cancel_keyboard

    order_id = callback.data.split(":")[2]
    await state.set_state(PurchaseStates.coupon)
    await state.update_data(order_id=order_id)
    await render(
        callback,
        await texts.text("input_coupon"),
        await cancel_keyboard(f"ord:invoice:{order_id}"),
    )
    await callback.answer()


@router.message(PurchaseStates.coupon)
async def apply_coupon(message: Message, state: FSMContext) -> None:
    data = await state.get_data()
    order_id = data.get("order_id")
    if not order_id or not message.text:
        return

    try:
        order = await api.apply_coupon(message.from_user.id, order_id, message.text.strip())
    except ApiError as exc:
        await message.answer(await texts.error(exc.code))
        return

    await state.set_state(None)
    await message.answer(await texts.text("coupon_applied", discount=order["discount"]))
    await show_invoice(message, order, data.get("options", []))


# -- flow options (gift hide / comment) ------------------------------
@router.callback_query(F.data.startswith("opt:"))
async def change_option(callback: CallbackQuery, state: FSMContext) -> None:
    _, key, value, order_id = callback.data.split(":", 3)
    data = await state.get_data()

    if value == "menu":
        options = data.get("options", [])
        option = next((item for item in options if item["key"] == key), None)
        choices = (option or {}).get("config", {}).get("choices", [])
        premium_price = await texts.setting("gift_premium_comment_price", 0)
        await render(
            callback,
            await texts.text("comment_type"),
            await comment_type_keyboard(order_id, choices, premium_price),
        )
        await callback.answer()
        return

    order = await api.update_inputs(callback.from_user.id, order_id, {key: bool(int(value))})
    await callback.answer(
        await texts.text("hide_enabled" if int(value) else "hide_disabled")
    )
    await show_invoice(callback, order, data.get("options", []))


@router.callback_query(F.data.startswith("cmt:"))
async def choose_comment_type(callback: CallbackQuery, state: FSMContext) -> None:
    _, value, order_id = callback.data.split(":", 2)
    await api.update_inputs(callback.from_user.id, order_id, {"comment_type": value})

    from bot.keyboards.common import cancel_keyboard

    await state.set_state(PurchaseStates.comment)
    await state.update_data(order_id=order_id)
    await render(
        callback,
        await texts.text("input_comment", max=200),
        await cancel_keyboard(f"ord:invoice:{order_id}"),
    )
    await callback.answer()


@router.message(PurchaseStates.comment)
async def save_comment(message: Message, state: FSMContext) -> None:
    data = await state.get_data()
    order_id = data.get("order_id")
    if not order_id or not message.text:
        return

    try:
        order = await api.update_inputs(
            message.from_user.id, order_id, {"comment": message.text.strip()}
        )
    except ApiError as exc:
        await message.answer(await texts.error(exc.code))
        return

    await state.set_state(None)
    await message.answer(await texts.text("comment_saved"))
    await show_invoice(message, order, data.get("options", []))


# -- wallet top-up (same checkout, no product) -----------------------
@router.callback_query(F.data == "menu:topup")
async def start_topup(callback: CallbackQuery, state: FSMContext) -> None:
    from bot.keyboards.common import cancel_keyboard

    await state.clear()
    await state.set_state(TopupStates.amount)
    minimum = await texts.setting("min_topup_amount", 0)
    await render(
        callback, await texts.text("input_topup_amount", min=minimum), await cancel_keyboard()
    )
    await callback.answer()


@router.message(TopupStates.amount)
async def create_topup_order(message: Message, state: FSMContext) -> None:
    if not message.text:
        return
    try:
        order = await api.create_order(
            message.from_user.id,
            {"flow": "topup", "input_data": {"amount": message.text.strip()}},
        )
    except ApiError as exc:
        await message.answer(await texts.error(exc.code))
        return

    await state.set_state(None)
    await state.update_data(order_id=order["id"], options=[])
    await show_invoice(message, order, [])


# -- recent orders ---------------------------------------------------
@router.callback_query(F.data == "menu:orders")
async def recent_orders(callback: CallbackQuery, state: FSMContext) -> None:
    await state.clear()
    result = await api.orders(callback.from_user.id)
    items = result.get("items", [])
    if not items:
        await render(callback, await texts.text("no_orders"), await main_menu_keyboard())
    else:
        await render(callback, await texts.text("recent_orders"), await orders_keyboard(items))
    await callback.answer()
