"""
Обработчики создания заявки.

Поток:
1. "Создать заявку"  → waiting_description
2. Первое сообщение  → сохранение описания, collecting_attachments
3. Доп. сообщения    → накопление вложений
4. "Готово"          → предпросмотр + кнопки подтверждения, confirming
5a. "Подтвердить"    → сохранение в БД + Intradesk, сообщение с номером, главное меню
5b. "Продолжить редактирование" → обратно в collecting_attachments
5c. "Отменить создание" → очистка состояния, главное меню
"""
from maxapi.context import BaseContext
from maxapi.dispatcher import Router
from maxapi.enums.attachment import AttachmentType
from maxapi.filters import F
from maxapi.types.attachments.attachment import (
    OtherAttachmentPayload,
    PhotoAttachmentPayload,
)
from maxapi.types.updates.message_callback import MessageCallback
from maxapi.types.updates.message_created import MessageCreated

from bot.flow_config import PAYLOADS, TRANSITIONS
from bot.handlers.menu import build_main_menu_text
from bot.keyboards import confirm_ticket_kb, done_ticket_kb, main_menu_kb
from bot.logging_setup import get_logger
from bot.nav_helper import nav_set
from bot.send_helper import bot_send
from bot.texts import MenuTexts, TicketTexts
from database.connection import get_db
from database.queries.intradesk import get_intradesk_contact_id_for_phone
from database.queries.organizations import get_organization
from database.queries.ticket_attachments import add_attachment
from database.queries.tickets import create_ticket, set_intradesk_task_id
from external.intradesk_api import create_intradesk_task, invalidate_client_cache, upload_attachments

logger = get_logger("bot.handlers.create_ticket")

router = Router(router_id="create_ticket")


async def cleanup_ticket_flow(bot, context: BaseContext) -> None:
    """
    Удалить висящие сообщения незавершённого потока создания заявки.
    Вызывается из любого обработчика, который прерывает этот поток
    (главное меню, список заявок, повторное нажатие «Создать заявку»).
    """
    from bot.states import TicketStates, CommentStates  # avoid circular at module level
    state = await context.get_state()
    creation_states = [
        TicketStates.waiting_description,
        TicketStates.collecting_attachments,
        TicketStates.confirming,
        CommentStates.waiting_text,
    ]
    if state not in creation_states:
        return
    data = await context.get_data()
    for mid in (data.get("flow_message_ids") or []):
        try:
            await bot.delete_message(message_id=mid)
        except Exception:
            pass
    await context.clear()


async def _track_mid(context: BaseContext, mid: str | None) -> None:
    """Добавить message_id в список сообщений потока создания."""
    if not mid:
        return
    data = await context.get_data()
    ids: list[str] = data.get("flow_message_ids") or []
    ids.append(mid)
    data["flow_message_ids"] = ids
    await context.set_data(data)


async def _delete_flow_messages(bot, context: BaseContext, extra_mid: str | None = None) -> None:
    """Удалить все сообщения, накопленные за время потока создания."""
    data = await context.get_data()
    ids: list[str] = list(data.get("flow_message_ids") or [])
    if extra_mid:
        ids.append(extra_mid)
    for mid in ids:
        try:
            await bot.delete_message(message_id=mid)
        except Exception:
            pass  # сообщение уже удалено или слишком старое

_MEDIA_TYPES = {
    AttachmentType.IMAGE,
    AttachmentType.VIDEO,
    AttachmentType.AUDIO,
    AttachmentType.FILE,
}


def _extract_attachments(event: MessageCreated) -> list[dict]:
    """Извлекает метаданные вложений из сообщения."""
    result = []
    body = event.message.body
    if not body or not body.attachments:
        return result
    for att in body.attachments:
        att_type = getattr(att, "type", None)
        if att_type not in _MEDIA_TYPES:
            continue
        url: str | None = None
        filename: str | None = None
        duration: int | None = None
        payload = getattr(att, "payload", None)
        if isinstance(payload, PhotoAttachmentPayload):
            url = payload.url
        elif isinstance(payload, OtherAttachmentPayload):
            url = payload.url
            filename = (
                getattr(payload, "filename", None)
                or getattr(payload, "name", None)
                or getattr(payload, "title", None)
            )
        if att_type == AttachmentType.AUDIO:
            duration = (
                getattr(payload, "duration", None)
                or getattr(payload, "duration_ms", None)
            )
        result.append({"type": str(att_type), "url": url, "filename": filename, "duration": duration})
    return result


def _att_label(att: dict) -> str:
    """Человекочитаемое описание вложения для предпросмотра."""
    t = att.get("type", "")
    if AttachmentType.IMAGE.value in t or "PHOTO" in t.upper():
        return TicketTexts.ATT_PHOTO
    if AttachmentType.VIDEO.value in t:
        return TicketTexts.ATT_VIDEO
    if AttachmentType.AUDIO.value in t:
        dur = att.get("duration")
        if dur:
            mins, secs = divmod(int(dur), 60)
            return TicketTexts.ATT_AUDIO_DUR.format(mins=mins, secs=secs)
        return TicketTexts.ATT_AUDIO
    if AttachmentType.FILE.value in t:
        fname = att.get("filename")
        return TicketTexts.ATT_FILE.format(fname=fname) if fname else TicketTexts.ATT_FILE_UNNAMED
    return TicketTexts.ATT_OTHER


def _preview_text(org_name: str | None, description: str, pending_atts: list[dict]) -> str:
    """Формирует текст предпросмотра заявки."""
    task_name = org_name or description[:80]
    lines = [
        TicketTexts.PREVIEW_HEADER,
        "",
        f"📄 {task_name}",
        "",
        description[:500] + ("…" if len(description) > 500 else ""),
    ]
    if pending_atts:
        lines += ["", TicketTexts.PREVIEW_ATTACHMENTS_HEADER]
        for att in pending_atts:
            lines.append(f"  • {_att_label(att)}")
    lines += ["", TicketTexts.PREVIEW_CONFIRM_PROMPT]
    return "\n".join(lines)


# ── Шаг 1: начало создания ────────────────────────────────────────────────────

@router.message_callback(F.callback.payload == PAYLOADS.CREATE_TICKET)
async def cb_create_ticket(
    event: MessageCallback, context: BaseContext, bot_user: dict
) -> None:
    # Зачищаем предыдущий незавершённый поток, если был
    await cleanup_ticket_flow(event.bot, context)

    await context.set_state(TRANSITIONS["ticket:start"])
    await context.set_data({"pending_attachments": [], "flow_message_ids": []})
    logger.info("📝 create_ticket started | user_id=%s", bot_user["max_user_id"])
    await event.answer(notification="")

    chat_id = event.message.recipient.chat_id if event.message else None
    if chat_id is None:
        return

    mid = await bot_send(
        event.bot,
        chat_id=chat_id,
        user_id=bot_user["max_user_id"],
        text=TicketTexts.ENTER_DESCRIPTION,
        attachments=[done_ticket_kb()],
    )
    await _track_mid(context, mid)


# ── Шаг 2: первое сообщение с описанием ──────────────────────────────────────

@router.message_created(states=TRANSITIONS["ticket:start"])
async def on_ticket_description(
    event: MessageCreated, context: BaseContext
) -> None:
    chat_id, user_id = event.get_ids()
    if chat_id is None:
        return

    body = event.message.body
    text = (body.text or "").strip() if body else ""
    new_atts = _extract_attachments(event)

    if not text:
        if not new_atts:
            # Стикер/локация/etc — просим написать описание
            mid = await bot_send(
                event.bot,
                chat_id=chat_id,
                user_id=user_id,
                text=TicketTexts.NEED_TEXT,
                attachments=[done_ticket_kb()],
            )
            await _track_mid(context, mid)
            return
        # Файл без текста — сохраняем, остаёмся в waiting_description, просим текст
        data = await context.get_data()
        existing = data.get("pending_attachments") or []
        existing.extend(new_atts)
        await context.update_data(pending_attachments=existing)
        mid = await bot_send(
            event.bot,
            chat_id=chat_id,
            user_id=user_id,
            text=TicketTexts.FILE_RECEIVED_NEED_TEXT,
            attachments=[done_ticket_kb()],
        )
        await _track_mid(context, mid)
        return

    # Есть текст — объединяем с ранее добавленными вложениями
    data = await context.get_data()
    existing_atts = data.get("pending_attachments") or []
    existing_atts.extend(new_atts)

    await context.update_data(description=text, pending_attachments=existing_atts)
    await context.set_state(TRANSITIONS["ticket:description_ok"])

    mid = await bot_send(
        event.bot,
        chat_id=chat_id,
        user_id=user_id,
        text=TicketTexts.DESCRIPTION_ACCEPTED,
        attachments=[done_ticket_kb()],
    )
    await _track_mid(context, mid)


# ── Шаг 3: дополнительные вложения/текст ─────────────────────────────────────

@router.message_created(states=TRANSITIONS["ticket:description_ok"])
async def on_ticket_attachment(
    event: MessageCreated, context: BaseContext
) -> None:
    data = await context.get_data()
    body = event.message.body
    extra_text = (body.text or "").strip() if body else ""
    new_atts = _extract_attachments(event)

    if extra_text:
        current = data.get("description", "")
        data["description"] = (current + "\n" + extra_text).strip()

    pending = data.get("pending_attachments", [])
    pending.extend(new_atts)
    data["pending_attachments"] = pending
    await context.set_data(data)


# ── Шаг 4: "Готово" → предпросмотр ───────────────────────────────────────────

@router.message_callback(
    F.callback.payload == PAYLOADS.DONE_TICKET,
    states=[TRANSITIONS["ticket:start"], TRANSITIONS["ticket:description_ok"]],
)
async def cb_done_ticket(
    event: MessageCallback, context: BaseContext, bot_user: dict
) -> None:
    data = await context.get_data()
    description = (data.get("description") or "").strip()

    chat_id = event.message.recipient.chat_id if event.message else None
    if chat_id is None:
        return

    if not description:
        await event.answer(notification=TicketTexts.PREVIEW_NEED_TEXT_NOTIF)
        await bot_send(
            event.bot,
            chat_id=chat_id,
            user_id=bot_user["max_user_id"],
            text=TicketTexts.PREVIEW_NEED_TEXT,
            attachments=[done_ticket_kb()],
        )
        return

    await event.answer(notification="")
    pending_atts = data.get("pending_attachments", [])

    org = await get_organization(bot_user["org_id"])
    org_name = org["name"] if org else None

    await context.set_state(TRANSITIONS["ticket:preview"])

    mid = await bot_send(
        event.bot,
        chat_id=chat_id,
        user_id=bot_user["max_user_id"],
        text=_preview_text(org_name, description, pending_atts),
        attachments=[confirm_ticket_kb()],
    )
    await _track_mid(context, mid)


# ── Шаг 5б: "Продолжить редактирование" ──────────────────────────────────────

@router.message_callback(
    F.callback.payload == PAYLOADS.EDIT_TICKET,
    states=TRANSITIONS["ticket:preview"],
)
async def cb_edit_ticket(
    event: MessageCallback, context: BaseContext, bot_user: dict
) -> None:
    await event.answer(notification="")
    await context.set_state(TRANSITIONS["ticket:edit"])

    chat_id = event.message.recipient.chat_id if event.message else None
    if chat_id is None:
        return

    mid = await bot_send(
        event.bot,
        chat_id=chat_id,
        user_id=bot_user["max_user_id"],
        text=TicketTexts.CONTINUE_EDITING,
        attachments=[done_ticket_kb()],
    )
    await _track_mid(context, mid)


# ── Шаг 5в: "Отменить создание" ──────────────────────────────────────────────

@router.message_callback(
    F.callback.payload == PAYLOADS.CANCEL_TICKET,
    states=[
        TRANSITIONS["ticket:start"],
        TRANSITIONS["ticket:description_ok"],
        TRANSITIONS["ticket:preview"],
    ],
)
async def cb_cancel_ticket(
    event: MessageCallback, context: BaseContext, bot_user: dict
) -> None:
    await event.answer(notification=TicketTexts.CANCEL_NOTIF)

    chat_id = event.message.recipient.chat_id if event.message else None
    if chat_id is None:
        return

    # Удаляем все сообщения потока + текущее сообщение с кнопками
    callback_mid = event.message.body.mid if (event.message and event.message.body) else None
    await _delete_flow_messages(event.bot, context, extra_mid=callback_mid)
    await context.clear()

    loading_mid = await bot_send(
        event.bot,
        chat_id=chat_id,
        user_id=bot_user["max_user_id"],
        text=MenuTexts.LOADING,
    )

    menu_text = await build_main_menu_text(bot_user)
    edited = False
    if loading_mid:
        try:
            await event.bot.edit_message(
                message_id=loading_mid,
                text=menu_text,
                attachments=[main_menu_kb()],
            )
            edited = True
            await nav_set(context, loading_mid)
        except Exception:
            pass
    if not edited:
        mid = await bot_send(
            event.bot,
            chat_id=chat_id,
            user_id=bot_user["max_user_id"],
            text=menu_text,
            attachments=[main_menu_kb()],
        )
        await nav_set(context, mid)


# ── Шаг 5а: "Подтвердить" → создание ─────────────────────────────────────────

@router.message_callback(
    F.callback.payload == PAYLOADS.CONFIRM_TICKET,
    states=TRANSITIONS["ticket:preview"],
)
async def cb_confirm_ticket(
    event: MessageCallback, context: BaseContext, bot_user: dict
) -> None:
    # Защита от двойного нажатия: читаем данные и сбрасываем состояние атомарно.
    # Повторный tap уже не пройдёт фильтр states=confirming.
    data = await context.get_data()
    # Удаляем все сообщения потока + текущее (предпросмотр с кнопками)
    callback_mid = event.message.body.mid if (event.message and event.message.body) else None
    await _delete_flow_messages(event.bot, context, extra_mid=callback_mid)
    await context.clear()

    description = (data.get("description") or "").strip()
    pending_atts = data.get("pending_attachments", [])

    chat_id = event.message.recipient.chat_id if event.message else None
    if chat_id is None:
        return

    loading_mid = await bot_send(
        event.bot,
        chat_id=chat_id,
        user_id=bot_user["max_user_id"],
        text=TicketTexts.SENDING,
    )

    # ── Загружаем вложения в Intradesk ────────────────────────────────────────
    if pending_atts:
        pending_atts = await upload_attachments(pending_atts, task_id=0, target="Description")

    # ── Данные организации ────────────────────────────────────────────────────
    org = await get_organization(bot_user["org_id"])
    org_name = org["name"] if org else None
    intradesk_id: int | None = org.get("intradesk_id") if org else None

    contact_person_id: int | None = None
    if intradesk_id and bot_user.get("phone"):
        db = get_db()
        contact_person_id = await get_intradesk_contact_id_for_phone(
            db, intradesk_id, bot_user["phone"]
        )

    # ── Сохраняем локально ────────────────────────────────────────────────────
    ticket_id = await create_ticket(
        org_id=bot_user["org_id"],
        created_by=bot_user["id"],
        description=description,
    )
    for att in pending_atts:
        await add_attachment(
            ticket_id=ticket_id,
            attachment_type=att["type"],
            attachment_url=att.get("url"),
        )
    logger.info(
        "✅ ticket created | ticket_id=%s user_id=%s org_id=%s attachments=%s",
        ticket_id, bot_user["max_user_id"], bot_user["org_id"], len(pending_atts),
    )

    # ── Отправка в Intradesk (ждём, чтобы получить номер) ────────────────────
    task_name = org_name or description[:150]

    intradesk_result = await create_intradesk_task(
        name=task_name,
        description=description,
        client_intradesk_id=intradesk_id,
        contact_person_id=contact_person_id,
        attachments=pending_atts or None,
    )

    intradesk_number: int | None = None
    if intradesk_result:
        iid = (
            intradesk_result.get("id") or intradesk_result.get("Id")
            or intradesk_result.get("ID")
        )
        intradesk_number = (
            intradesk_result.get("tasknumber") or intradesk_result.get("number")
            or intradesk_result.get("Number") or intradesk_result.get("TaskNumber")
        )
        if iid:
            await set_intradesk_task_id(ticket_id, int(iid))
            logger.info(
                "Intradesk task created: id=%s number=%s for ticket_id=%s",
                iid, intradesk_number, ticket_id,
            )
        if intradesk_id:
            invalidate_client_cache(intradesk_id)

    # ── Итоговое сообщение ────────────────────────────────────────────────────
    lines = [TicketTexts.CREATED]
    if intradesk_number:
        lines.append(TicketTexts.CREATED_NUMBER.format(number=intradesk_number))
    lines += ["", f"📄 {task_name}", ""]
    lines.append(description[:500] + ("…" if len(description) > 500 else ""))
    if pending_atts:
        lines += ["", TicketTexts.PREVIEW_ATTACHMENTS_HEADER]
        for att in pending_atts:
            lines.append(f"  • {_att_label(att)}")
    lines += ["", TicketTexts.CREATED_CONTACT]

    final_text = "\n".join(lines)
    edited = False
    if loading_mid:
        try:
            await event.bot.edit_message(
                message_id=loading_mid,
                text=final_text,
                attachments=[main_menu_kb()],
            )
            edited = True
            await nav_set(context, loading_mid)
        except Exception:
            pass
    if not edited:
        mid = await bot_send(
            event.bot,
            chat_id=chat_id,
            user_id=bot_user["max_user_id"],
            text=final_text,
            attachments=[main_menu_kb()],
        )
        await nav_set(context, mid)
