# -*- coding: utf-8 -*-
# hub/ticket/ui/ticket_create_page.py

"""Форма создания заявки в составе независимого приложения Dispatch.

Назначение модуля:
    Страница «Создать заявку», разделённая на две колонки:

    - Левая колонка занимает 70% ширины и содержит выпадающий список
      программного продукта, выпадающий список краткого заголовка
      неисправности и многострочное поле подробного описания
      неисправности.
    - Правая колонка занимает 30% ширины и содержит блок действий
      формы: «Создать заявку», «Отмена», «Прикрепить файл».

Источники данных:
    - `DB_dispatch/3_software_list.py` — справочник программных
      продуктов (ключ `software_id` → наименование).
    - `DB_dispatch/4_malfunction_list.py` — справочник кратких
      заголовков неисправностей (ключ `software_id` → перечень
      формулировок).
    - `DB_dispatch/0_users.py` и `DB_dispatch/2_customer_facility_list.py`
      — учётная запись и место установки заявителя; используются
      для формирования строки локации заявки.

Архитектурные ограничения:
    - QSS-литералы и фиксированные размеры в прикладном коде не
      используются: оформление подаётся через ключи `APP_STYLES`,
      геометрия задаётся процентными долями.
    - Все элементы построены на канонических обёртках `Button`,
      `Label`, `ComboBox`, `TextInput`, `SContainer`, `HContainer`,
      `VContainer`.
    - Доступ к каталогу `DB_dispatch` осуществляется только через
      сервис `hub.my_account.auth_service`.
"""

from __future__ import annotations

from datetime import datetime
from typing import Callable

from gui.components import Button, ComboBox, Label, TextInput
from gui.containers import HContainer, SContainer, VContainer

from application import TaskApplicationService
from domain import TicketTaskSnapshot
from domain.location_catalog import parse_location_parts
from domain.ticket_constants import (
    STATE_TODO,
    TICKET_STATE_ACTIONS,
    TICKET_STATE_COLORS,
    TICKET_STATE_NAMES,
)


# Справочные тексты в комбобоксах: используются как placeholder,
# чтобы пользователю было понятно назначение поля до открытия списка.
_PRODUCT_PLACEHOLDER = "Программный продукт"
_SUMMARY_PLACEHOLDER = "Краткий заголовок неисправности"


class TicketCreatePage(SContainer):
    """Двухколоночная форма создания заявки и её отправки на доску `STATE_TODO`."""

    def __init__(
        self,
        application: TaskApplicationService,
        on_finish: Callable[[], None],
        parent=None,
    ):
        super().__init__(
            width_percent=100,
            height_percent=100,
            margin=0,
            parent=parent,
            style="TICKET_SHELL_ROOT",
        )
        self._application = application
        self._on_finish = on_finish

        # Состояние активной сессии заявителя; используется только для
        # формирования строки локации, в интерфейсе не отображается.
        self._signed_user: dict | None = None
        self._user_institution: str = ""
        self._user_room: str = ""

        # Справочные таблицы DB_dispatch и индекс «наименование → id»
        # для определения списка неисправностей по выбранному продукту.
        self._software_by_id: dict[str, str] = {}
        self._software_id_by_name: dict[str, str] = {}
        self._malfunction_by_software_id: dict[str, list[str]] = {}

        # Поля левой колонки.
        self._product_combo: ComboBox | None = None
        self._summary_combo: ComboBox | None = None
        self._description_input: TextInput | None = None

        # Действия формы.
        self._submit_button: Button | None = None
        self._cancel_button: Button | None = None
        self._attach_button: Button | None = None
        self._error_label: Label | None = None

        self._setup_ui()
        self._connect_signals()
        self._load_reference_data()

    # ── Сборка интерфейса ───────────────────────────────────────────────

    def _setup_ui(self) -> None:
        # Корневой контейнер страницы — горизонтальная компоновка с
        # явными долями 70% (левая колонка) и 30% (правая колонка).
        # `HContainer` всегда занимает 100% ширины родителя, поэтому
        # параметр `width_percent` ему не передаётся.
        root = HContainer(
            height_percent=100,
            margin=[24, 18, 24, 18],
            spacing=18,
            content_fit=False,
            parent=self,
        )

        # ── Левая колонка: поля заявки (70%). ────────────────────────
        # `VContainer` всегда занимает 100% высоты родителя, поэтому
        # `height_percent` опускается; ширина колонки задаётся явно.
        left = VContainer(
            width_percent=70,
            margin=0,
            spacing=12,
            content_fit=False,
            parent=root,
        )

        left.add_widget(Label(
            "Создать заявку",
            height_percent=8,
            style="LOGIN_TITLE",
        ))

        # Комбобокс программного продукта: справочный текст-подсказка
        # отображается в редактируемой строке до выбора пункта.
        self._product_combo = ComboBox(
            width_percent=100,
            height_percent=8,
            content_fit=False,
        )
        self._product_combo.set_editable(True)
        self._product_combo.set_placeholder_text(_PRODUCT_PLACEHOLDER)
        left.add_widget(self._product_combo)

        # Комбобокс краткого заголовка неисправности: пункты списка
        # обновляются после выбора программного продукта.
        self._summary_combo = ComboBox(
            width_percent=100,
            height_percent=8,
            content_fit=False,
        )
        self._summary_combo.set_editable(True)
        self._summary_combo.set_placeholder_text(_SUMMARY_PLACEHOLDER)
        left.add_widget(self._summary_combo)

        self._description_input = TextInput(
            placeholder=(
                "Подробно опишите проявления неисправности, шаги "
                "воспроизведения, коды ошибок и предпринятые действия."
            ),
            width_percent=100,
            height_percent=68,
            content_fit=False,
            multiline=True,
        )
        left.add_widget(self._description_input)

        # ── Правая колонка: блок действий (30%). ──────────────────────
        right = VContainer(
            width_percent=30,
            margin=0,
            spacing=12,
            content_fit=False,
            parent=root,
        )

        self._error_label = Label(
            "",
            height_percent=8,
            style="LOGIN_ERROR_LABEL",
        )
        self._error_label.set_visible(False)
        right.add_widget(self._error_label)

        right.add_stretch(1)

        # Блок действий: «Создать заявку», «Отмена», «Прикрепить файл».
        # `VContainer` принимает только ширину в процентах от родителя;
        # высота блока определяется собственными процентами кнопок.
        actions = VContainer(
            width_percent=100,
            margin=0,
            spacing=8,
            content_fit=False,
            parent=right,
        )
        self._submit_button = Button(
            "Создать заявку",
            width_percent=100,
            height_percent=30,
            margin=0,
            style="LOGIN_SUBMIT_BUTTON",
            content_fit=False,
        )
        self._cancel_button = Button(
            "Отмена",
            width_percent=100,
            height_percent=30,
            margin=0,
            style="LOGIN_CANCEL_BUTTON",
            content_fit=False,
        )
        self._attach_button = Button(
            "Прикрепить файл",
            width_percent=100,
            height_percent=30,
            margin=0,
            style="LOGIN_NAV_BUTTON",
            content_fit=False,
        )
        actions.add_widget(self._submit_button)
        actions.add_widget(self._cancel_button)
        actions.add_widget(self._attach_button)
        right.add_widget(actions)

    # ── Сигналы и справочные данные ─────────────────────────────────────

    def _connect_signals(self) -> None:
        if self._submit_button is not None:
            self._submit_button.clicked.connect(self._on_submit_clicked)
        if self._cancel_button is not None:
            self._cancel_button.clicked.connect(self._on_cancel_clicked)
        if self._attach_button is not None:
            self._attach_button.clicked.connect(self._on_attach_clicked)
        if self._product_combo is not None:
            self._product_combo.current_text_changed.connect(
                self._on_product_changed,
            )

    def _load_reference_data(self) -> None:
        """Загрузить справочники программных продуктов и неисправностей.

        Источники — `DB_dispatch/3_software_list.py` и
        `DB_dispatch/4_malfunction_list.py`. Полученные таблицы
        используются для наполнения комбобоксов и для определения
        перечня неисправностей по выбранному программному продукту.
        """
        # Импорт внутри метода исключает циклическую зависимость
        # между UI-слоем Ticket и сервисом учётных записей Dispatch.
        from auth_service import (
            load_malfunction_list,
            load_software_list,
        )

        self._software_by_id = load_software_list()
        self._malfunction_by_software_id = load_malfunction_list()
        self._software_id_by_name = {
            name: software_id
            for software_id, name in self._software_by_id.items()
        }
        self._populate_product_combo()
        self._populate_summary_combo("")

    def _populate_product_combo(self) -> None:
        if self._product_combo is None:
            return
        names = sorted(self._software_by_id.values())
        self._product_combo.set_items(["", *names])
        self._product_combo.set_index(0)

    def _populate_summary_combo(self, software_name: str) -> None:
        if self._summary_combo is None:
            return
        software_id = self._software_id_by_name.get(software_name, "")
        if software_id:
            titles = list(self._malfunction_by_software_id.get(software_id, []))
        else:
            # Пока программный продукт не выбран, показываем полный
            # перечень заголовков из `DB_dispatch/4_malfunction_list.py`,
            # сохраняя порядок появления и убирая дубликаты.
            titles = []
            seen: set[str] = set()
            for values in self._malfunction_by_software_id.values():
                for title in values:
                    if title in seen:
                        continue
                    seen.add(title)
                    titles.append(title)
        self._summary_combo.set_items(["", *titles])
        self._summary_combo.set_index(0)

    def _on_product_changed(self, text: str) -> None:
        """Обновить набор кратких заголовков под выбранный продукт."""
        self._populate_summary_combo((text or "").strip())

    def refresh_user_session(self) -> None:
        """Подтянуть реквизиты заявителя из активной сессии Dispatch.

        Метод сохраняет данные пользователя только во внутренних полях:
        учреждение и кабинет нужны для формирования строки локации
        заявки. В интерфейсе реквизиты заявителя не отображаются.
        """
        from auth_service import get_user_facility, load_session

        user = load_session()
        self._signed_user = user
        if user is None:
            self._user_institution = ""
            self._user_room = ""
            return

        institution, room, _product = parse_location_parts(get_user_facility(user))
        self._user_institution = institution
        self._user_room = room

    # ── Обработчики действий ────────────────────────────────────────────

    def _on_cancel_clicked(self, _checked: bool = False) -> None:
        """Сбросить форму и вернуть пользователя на доску заявок."""
        self._reset_form()
        self._on_finish()

    def _on_attach_clicked(self, _checked: bool = False) -> None:
        """Заглушка действия прикрепления файла.

        Полнофункциональная загрузка вложений выходит за рамки
        текущего этапа; кнопка зарезервирована в разметке и
        подключает обработчик-уведомление, чтобы пользователь не
        воспринимал отсутствие реакции как сбой.
        """
        self._show_error("Прикрепление файла будет доступно на следующем этапе.")

    def _on_submit_clicked(self, _checked: bool = False) -> None:
        """Проверить поля, собрать snapshot и зарегистрировать заявку."""
        if self._signed_user is None:
            self._show_error("Войдите в систему перед созданием заявки.")
            return

        product = self._read_combo_text(self._product_combo)
        summary = self._read_combo_text(self._summary_combo)
        description = (
            self._description_input.get_text() if self._description_input else ""
        ).strip()

        if not product or not summary or not description:
            self._show_error(
                "Выберите программный продукт, краткий заголовок и заполните описание.",
            )
            return
        if not self._user_institution:
            self._show_error(
                "Для учётной записи не задано место установки в DB_dispatch.",
            )
            return

        location = self._compose_location(product, summary)
        snapshot = self._build_snapshot(location)
        self._application.submit_new_task(snapshot)
        self._reset_form()
        self._hide_error()
        self._on_finish()

    @staticmethod
    def _read_combo_text(combo: ComboBox | None) -> str:
        if combo is None:
            return ""
        return combo.get_current_text().strip()

    def _compose_location(self, product: str, summary: str) -> str:
        # Формат локации совпадает с парсером `parse_location_parts`:
        # «Учреждение (Аппарат — заголовок, каб. №)». Это даёт корректное
        # отображение карточки на доске без дополнительных адаптеров.
        device_segment = f"{product} — {summary}" if summary else product
        if not self._user_room:
            return f"{self._user_institution} ({device_segment})"
        return f"{self._user_institution} ({device_segment}, {self._user_room})"

    def _build_snapshot(self, location: str) -> TicketTaskSnapshot:
        action_text = TICKET_STATE_ACTIONS.get(STATE_TODO, "")
        return TicketTaskSnapshot(
            task_id=self._application.allocate_new_task_id(),
            location=location,
            state_code=STATE_TODO,
            state_name=TICKET_STATE_NAMES.get(STATE_TODO, ""),
            action_text=action_text,
            color_hex=TICKET_STATE_COLORS.get(STATE_TODO, "#FFFFFF"),
            created_at=datetime.now(),
        )

    def _reset_form(self) -> None:
        """Очистить редактируемые поля левой колонки."""
        if self._product_combo is not None:
            self._product_combo.set_index(0)
        if self._summary_combo is not None:
            self._summary_combo.set_index(0)
        if self._description_input is not None:
            self._description_input.clear()
        self._hide_error()

    # ── Сообщения об ошибках ────────────────────────────────────────────

    def _show_error(self, message: str) -> None:
        if self._error_label is None:
            return
        self._error_label.set_text(message)
        self._error_label.set_visible(True)

    def _hide_error(self) -> None:
        if self._error_label is None:
            return
        self._error_label.set_text("")
        self._error_label.set_visible(False)
