# app/src/telegram/handlers/journal.py
 
from __future__ import annotations

from aiogram import F, Router
from aiogram.exceptions import TelegramBadRequest, TelegramNetworkError
from aiogram.fsm.context import FSMContext
from aiogram.types import (
    BufferedInputFile,
    CallbackQuery,
    InaccessibleMessage,
    Message,
)

from src.core.numbers import safe_float
from src.core.types import JsonDict, NumericLike
from src.telegram.handlers.journal_ui import (
    PAGE_SIZE,
    build_actions_keyboard,
    build_clear_confirm_keyboard,
    build_export_format_keyboard,
    build_keyboard,
    render,
    render_actions,
    render_clear_confirm,
    render_export_format,
)
from src.trading.journal.filters import normalize_journal_export_filter
from src.telegram.live.active_screen import ActiveScreenManager
from src.telegram.live.runner import LiveScreenRunner, ScreenRegistry, StaticScreen
from src.trading.journal.service import JournalService


router = Router(name="journal")


def _require_message(
    callback: CallbackQuery,
) -> Message | None:
    message = callback.message

    if (
        message is None
        or isinstance(message, InaccessibleMessage)
    ):
        return None

    return message


def _user_id_from_message(message: Message) -> int | None:
    return message.from_user.id if message.from_user else None


def _chat_id_from_message(message: Message) -> int:
    return message.chat.id


def _user_id_from_callback(callback: CallbackQuery) -> int | None:
    return callback.from_user.id if callback.from_user else None


def _chat_id_from_callback(callback: CallbackQuery) -> int | None:
    message = _require_message(callback)

    if message is None:
        return None

    return message.chat.id


def _parse_page(value: NumericLike | None) -> int | None:
    number = safe_float(value)

    if number is None:
        return None

    return int(number)


def _journal_payload(**values: object) -> JsonDict:
    return dict(values)


def _register_journal_screen(message: Message) -> None:
    bot = message.bot

    if bot is None:
        return

    LiveScreenRunner.unregister_message(
        chat_id=message.chat.id,
        message_id=message.message_id,
    )

    ScreenRegistry.unregister_message(
        chat_id=message.chat.id,
        message_id=message.message_id,
    )

    ScreenRegistry.register_screen(
        StaticScreen(
            screen="journal",
            bot=bot,
            chat_id=message.chat.id,
            message_id=message.message_id,
        )
    )

    ActiveScreenManager.register(
        screen="journal",
        message=message,
    )


async def _prepare_journal_from_message(
    message: Message,
) -> bool:
    bot = message.bot

    if bot is None:
        return False

    await ActiveScreenManager.prepare_new_screen(
        screen="journal",
        bot=bot,
        chat_id=message.chat.id,
    )

    return True


async def _prepare_journal_from_callback(
    callback: CallbackQuery,
) -> bool:
    message = _require_message(callback)

    if message is None:
        await callback.answer(
            "Сообщение недоступно",
            show_alert=True,
        )
        return False

    bot = message.bot

    if bot is None:
        await callback.answer(
            "Bot недоступен",
            show_alert=True,
        )
        return False

    await ActiveScreenManager.prepare_new_screen(
        screen="journal",
        bot=bot,
        chat_id=message.chat.id,
        keep_message_id=message.message_id,
    )

    return True


async def _show_journal_page(
    target_message: Message,
    *,
    page: int,
    edit_mode: bool,
) -> None:
    service = JournalService()

    total = service.get_total_count()
    total_pages = max(1, (total + PAGE_SIZE - 1) // PAGE_SIZE)
    page = max(1, min(page, total_pages))

    events = service.get_page(page, PAGE_SIZE)

    text = render(events, page, total_pages)
    kb = build_keyboard(page, total_pages)

    if edit_mode:
        try:
            await target_message.edit_text(
                text,
                reply_markup=kb,
                parse_mode="HTML",
            )
        except TelegramBadRequest as exc:
            if "message is not modified" in str(exc).lower():
                _register_journal_screen(target_message)
                return

            raise

        _register_journal_screen(target_message)
        return

    sent_message = await target_message.answer(
        text,
        reply_markup=kb,
        parse_mode="HTML",
    )
    _register_journal_screen(sent_message)


@router.callback_query(F.data == "journal:actions")
async def journal_actions(callback: CallbackQuery) -> None:
    if not await _prepare_journal_from_callback(callback):
        return

    message = _require_message(callback)

    if message is None:
        await callback.answer("Сообщение недоступно", show_alert=True)
        return

    try:
        await message.edit_text(
            render_actions(),
            reply_markup=build_actions_keyboard(),
        )
    except TelegramBadRequest as exc:
        if "message is not modified" not in str(exc).lower():
            raise

    _register_journal_screen(message)

    await callback.answer()


@router.callback_query(F.data.startswith("journal:export_filter:"))
async def journal_export_filter(callback: CallbackQuery) -> None:
    # Пользователь выбрал фильтр экспорта.
    # Теперь показываем выбор формата: CSV или Excel.
    if not await _prepare_journal_from_callback(callback):
        return

    message = _require_message(callback)

    if message is None:
        await callback.answer("Сообщение недоступно", show_alert=True)
        return

    raw_filter = (callback.data or "").rsplit(":", 1)[-1]
    export_filter = normalize_journal_export_filter(raw_filter)

    try:
        await message.edit_text(
            render_export_format(export_filter),
            reply_markup=build_export_format_keyboard(export_filter),
        )
    except TelegramBadRequest as exc:
        if "message is not modified" not in str(exc).lower():
            raise

    _register_journal_screen(message)

    await callback.answer()


@router.message(F.text == "📒 Журнал")
async def open_journal(message: Message, state: FSMContext) -> None:
    await state.clear()

    if not await _prepare_journal_from_message(message):
        return

    await _show_journal_page(
        message,
        page=1,
        edit_mode=False,
    )


@router.callback_query(F.data == "system:journal")
async def open_journal_from_system(
    callback: CallbackQuery,
    state: FSMContext,
) -> None:
    await state.clear()

    if not await _prepare_journal_from_callback(callback):
        return

    message = _require_message(callback)

    if message is None:
        await callback.answer("Сообщение недоступно", show_alert=True)
        return

    await _show_journal_page(
        message,
        page=1,
        edit_mode=True,
    )

    await callback.answer()


@router.callback_query(F.data == "journal:noop")
async def journal_noop(callback: CallbackQuery) -> None:
    await callback.answer()


@router.callback_query(F.data.startswith("journal:export_csv"))
async def export_journal_csv(callback: CallbackQuery) -> None:
    service = JournalService()
    message = _require_message(callback)

    if message is None:
        await callback.answer("Сообщение недоступно", show_alert=True)
        return

    parts = (callback.data or "").split(":")
    export_filter = normalize_journal_export_filter(
        parts[2] if len(parts) >= 3 else "all"
    )

    await callback.answer("Готовлю CSV…")

    try:
        data = service.export_csv(export_filter=export_filter)
        document = BufferedInputFile(
            data,
            filename=service.build_export_filename(
                "csv",
                export_filter=export_filter,
            ),
        )

        await message.answer_document(
            document=document,
            request_timeout=120,
        )

        service.log_ui_info(
            event_type="journal_exported",
            message="Журнал экспортирован в CSV.",
            screen="journal",
            action="export_csv",
            user_id=_user_id_from_callback(callback),
            chat_id=_chat_id_from_callback(callback),
            payload=_journal_payload(
                format="csv",
                export_filter=export_filter,
            ),
        )

    except TelegramNetworkError as exc:
        service.log_ui_error(
            event_type="journal_export_error",
            message="Не удалось отправить CSV файл журнала.",
            screen="journal",
            action="export_csv",
            user_id=_user_id_from_callback(callback),
            chat_id=_chat_id_from_callback(callback),
            payload=_journal_payload(
                format="csv",
                export_filter=export_filter,
            ),
            raw_error=str(exc),
        )

        await message.answer(
            "⛔️ Не удалось отправить CSV файл.\n"
            "Попробуй ещё раз или уменьши объём журнала."
        )

    except Exception as exc:
        service.log_ui_error(
            event_type="journal_export_error",
            message="Не удалось экспортировать журнал в CSV.",
            screen="journal",
            action="export_csv",
            user_id=_user_id_from_callback(callback),
            chat_id=_chat_id_from_callback(callback),
            payload=_journal_payload(
                format="csv",
                export_filter=export_filter,
            ),
            raw_error=str(exc),
        )

        await message.answer("⛔️ Не удалось экспортировать CSV.")


@router.callback_query(F.data.startswith("journal:export_xlsx"))
async def export_journal_xlsx(callback: CallbackQuery) -> None:
    service = JournalService()
    message = _require_message(callback)

    if message is None:
        await callback.answer("Сообщение недоступно", show_alert=True)
        return

    parts = (callback.data or "").split(":")
    export_filter = normalize_journal_export_filter(
        parts[2] if len(parts) >= 3 else "all"
    )

    await callback.answer("Готовлю Excel…")

    try:
        data = service.export_xlsx(export_filter=export_filter)
        document = BufferedInputFile(
            data,
            filename=service.build_export_filename(
                "xlsx",
                export_filter=export_filter,
            ),
        )

        await message.answer_document(
            document=document,
            request_timeout=120,
        )

        service.log_ui_info(
            event_type="journal_exported",
            message="Журнал экспортирован в XLSX.",
            screen="journal",
            action="export_xlsx",
            user_id=_user_id_from_callback(callback),
            chat_id=_chat_id_from_callback(callback),
            payload=_journal_payload(
                format="xlsx",
                export_filter=export_filter,
            ),
        )

    except TelegramNetworkError as exc:
        service.log_ui_error(
            event_type="journal_export_error",
            message="Не удалось отправить XLSX файл журнала.",
            screen="journal",
            action="export_xlsx",
            user_id=_user_id_from_callback(callback),
            chat_id=_chat_id_from_callback(callback),
            payload=_journal_payload(
                format="xlsx",
                export_filter=export_filter,
            ),
            raw_error=str(exc),
        )

        await message.answer(
            "⛔️ Не удалось отправить Excel файл.\n"
            "Попробуй ещё раз или уменьши объём журнала."
        )

    except Exception as exc:
        service.log_ui_error(
            event_type="journal_export_error",
            message="Не удалось экспортировать журнал в XLSX.",
            screen="journal",
            action="export_xlsx",
            user_id=_user_id_from_callback(callback),
            chat_id=_chat_id_from_callback(callback),
            payload=_journal_payload(
                format="xlsx",
                export_filter=export_filter,
            ),
            raw_error=str(exc),
        )

        await message.answer("⛔️ Не удалось экспортировать Excel.")


@router.callback_query(F.data == "journal:clear_confirm")
async def clear_journal_confirm(callback: CallbackQuery) -> None:
    if not await _prepare_journal_from_callback(callback):
        return

    message = _require_message(callback)

    if message is None:
        await callback.answer("Сообщение недоступно", show_alert=True)
        return

    service = JournalService()
    total_count = service.get_total_count()

    await message.edit_text(
        render_clear_confirm(total_count=total_count),
        reply_markup=build_clear_confirm_keyboard(),
    )

    _register_journal_screen(message)

    await callback.answer()


@router.callback_query(F.data == "journal:clear")
async def clear_journal(callback: CallbackQuery) -> None:
    if not await _prepare_journal_from_callback(callback):
        return

    message = _require_message(callback)

    if message is None:
        await callback.answer("Сообщение недоступно", show_alert=True)
        return

    service = JournalService()
    deleted_count = service.clear_all()
    total_count = service.get_total_count()

    await message.edit_text(
        render_clear_confirm(
            total_count=total_count,
            deleted_count=deleted_count,
        ),
        reply_markup=build_clear_confirm_keyboard(),
    )

    _register_journal_screen(message)

    await callback.answer(f"Удалено: {deleted_count}")


@router.callback_query(F.data == "journal:clear_older:90")
async def clear_journal_older_90(callback: CallbackQuery) -> None:
    if not await _prepare_journal_from_callback(callback):
        return

    message = _require_message(callback)

    if message is None:
        await callback.answer("Сообщение недоступно", show_alert=True)
        return

    service = JournalService()
    deleted_count = service.clear_older_than_days(90)
    total_count = service.get_total_count()

    await message.edit_text(
        render_clear_confirm(
            total_count=total_count,
            deleted_count=deleted_count if deleted_count > 0 else None,
            no_old_records_days=90 if deleted_count == 0 else None,
        ),
        reply_markup=build_clear_confirm_keyboard(),
    )

    _register_journal_screen(message)

    await callback.answer(f"Удалено: {deleted_count}")


@router.callback_query(F.data.startswith("journal:"))
async def paginate(callback: CallbackQuery) -> None:
    if not await _prepare_journal_from_callback(callback):
        return

    message = _require_message(callback)

    if message is None:
        await callback.answer("Сообщение недоступно", show_alert=True)
        return

    data = callback.data or ""
    parts = data.split(":", 1)

    if len(parts) < 2:
        await callback.answer("Неизвестное действие", show_alert=True)
        return

    page = _parse_page(parts[1])

    if page is None:
        await callback.answer("Неизвестное действие", show_alert=True)
        return

    await _show_journal_page(
        message,
        page=page,
        edit_mode=True,
    )

    await callback.answer()