# -*- coding: utf-8 -*-
# hub/ticket/application/task_application_service.py

"""Единый application-фасад Ticket поверх state, domain и hardware gateway."""

from __future__ import annotations

from collections.abc import Mapping
from typing import Any

from PySide6.QtCore import QObject, Signal

from error_logger import log_exception

from domain import (
    ArchiveRecordSnapshot,
    TicketConnectionStatus,
    TicketDocumentSnapshot,
    TicketHardwareStatus,
    TicketStateService,
    TicketTaskSnapshot,
)
from domain.task import TicketTask
from domain.ticket_constants import STATE_COMPLETED, STATE_REFUSED
from services import ServiceManager, TicketHardwareGateway
from state import TicketRuntimeState
from .archive_service import ArchiveService
from .document_flow_service import DocumentFlowService
from .report_signing_service import ReportSigningService
from .specialist_assignment_service import SpecialistAssignmentService


class TaskApplicationService(QObject):
    """Канонический application facade Ticket."""

    task_updated = Signal(object)
    task_removed = Signal(int)
    connection_changed = Signal(str, str)
    active_view_changed = Signal(str)
    state_loaded = Signal()
    com_connection_changed = Signal(bool, str)
    button_initialization_changed = Signal(bool, int)

    def __init__(
        self,
        state: TicketRuntimeState | None = None,
        hardware_gateway: TicketHardwareGateway | None = None,
        state_service: TicketStateService | None = None,
        specialist_service: SpecialistAssignmentService | None = None,
        report_service: ReportSigningService | None = None,
        archive_service: ArchiveService | None = None,
        document_service: DocumentFlowService | None = None,
        parent: QObject | None = None,
    ):
        super().__init__(parent)
        self._state = state or TicketRuntimeState()
        self._hardware_gateway = hardware_gateway or ServiceManager(parent=self)
        self._state_service = state_service or TicketStateService()
        self._specialist_service = specialist_service or SpecialistAssignmentService(self._state)
        self._report_service = report_service or ReportSigningService(self._state)
        self._document_service = document_service or DocumentFlowService(self._state)
        self._archive_service = archive_service or ArchiveService(
            self._state,
            self._state_service,
            document_service=self._document_service,
        )
        self._started = False
        self._connect_state_signals()

    def start(self) -> None:
        """Загрузить state, синхронизировать gateway и запустить сервисы."""
        if self._started:
            return
        self._started = True
        self._state.load()
        self._hardware_gateway.reset_button_states()
        for task in self._state.list_tasks():
            self._hardware_gateway.set_button_state(task.task_id, task.state_code)
        self._hardware_gateway.set_observer(self)
        self._hardware_gateway.start()

    def stop(self) -> None:
        """Остановить gateway и снять observer."""
        if not self._started:
            return
        self._started = False
        self._hardware_gateway.set_observer(None)
        self._hardware_gateway.stop()

    def list_tasks(self) -> list[TicketTaskSnapshot]:
        return list(self._state.list_tasks())

    def list_active_tasks(self) -> list[TicketTaskSnapshot]:
        return list(self._state.list_active_tasks())

    def list_archived_tasks(self) -> list[TicketTaskSnapshot]:
        return list(self._state.list_archived_tasks())

    def list_archive_records(self) -> list[ArchiveRecordSnapshot]:
        return self._archive_service.list_archive_records()

    def get_task(self, task_id: int) -> TicketTaskSnapshot | None:
        return self._state.get_task(task_id)

    def handle_task_action(self, raw_action: Mapping[str, Any]) -> TicketTaskSnapshot | None:
        """Обработать аппаратное действие через доменную state-machine."""
        button_id = self._normalize_int(raw_action.get("button_id"))
        hardware_state = self._normalize_int(raw_action.get("hardware_state"))
        if button_id is None or hardware_state is None:
            return None

        current_snapshot = self._state.get_task(button_id)
        current_task = (
            TicketTask.from_snapshot(current_snapshot)
            if current_snapshot is not None
            else None
        )
        result = self._state_service.handle_hardware_signal(
            button_id,
            hardware_state,
            current_task,
        )
        if result.task is not None:
            self._assign_sequence_on_terminal_state(result.task)
            updated_snapshot = result.task.to_snapshot()
            self._state.upsert_task(updated_snapshot)
            self._hardware_gateway.set_button_state(
                updated_snapshot.task_id,
                updated_snapshot.state_code,
            )
            self._auto_save_archive_record(updated_snapshot)
            return updated_snapshot
        if current_snapshot is not None:
            self._hardware_gateway.set_button_state(
                current_snapshot.task_id,
                current_snapshot.state_code,
            )
        return current_snapshot

    def set_active_view(self, view_name: str) -> None:
        self._state.active_view = view_name

    def submit_new_task(self, snapshot: TicketTaskSnapshot) -> TicketTaskSnapshot | None:
        """Зарегистрировать заявку, созданную через форму Dispatch.

        Назначение: добавляет в runtime-state готовый снимок задачи в
        состоянии «Новая заявка» (`STATE_TODO`). Вызывает `upsert_task`,
        который эмитит `task_updated` — доска подхватывает карточку без
        дополнительной шины событий. Аппаратный шлюз в Dispatch
        отключён (`NullHardwareGateway`), поэтому синхронизация
        состояния кнопок не выполняется.
        """
        if snapshot is None:
            return None
        self._state.upsert_task(snapshot)
        return self._state.get_task(snapshot.task_id)

    def allocate_new_task_id(self) -> int:
        """Выдать свободный идентификатор для заявки, созданной диспетчером."""
        existing_ids = [task.task_id for task in self._state.list_tasks()]
        base = max(existing_ids, default=0)
        # Резервируем диапазон 1..99 за аппаратными button_id, чтобы при
        # будущей реальной интеграции с COM-каналом идентификаторы заявок
        # из формы не пересекались с физическими кнопками.
        return max(base, 99) + 1

    def assign_specialist(
        self,
        task_id: int,
        specialist_name: str,
    ) -> TicketTaskSnapshot | None:
        snapshot = self._specialist_service.assign_specialist(task_id, specialist_name)
        return self._sync_gateway_state(snapshot)

    def list_specialists(self) -> list[str]:
        return self._specialist_service.list_specialists()

    def sign_report(
        self,
        task_id: int,
        report_type: str,
    ) -> TicketTaskSnapshot | None:
        snapshot = self._report_service.sign_report(task_id, report_type)
        return self._sync_gateway_state(snapshot)

    def sign_acceptance_report(self, task_id: int) -> TicketTaskSnapshot | None:
        snapshot = self._report_service.sign_acceptance_report(task_id)
        return self._sync_gateway_state(snapshot)

    def archive_task(self, task_id: int) -> TicketTaskSnapshot | None:
        snapshot = self._archive_service.archive_task(task_id)
        return self._sync_gateway_state(snapshot)

    def refuse_task(
        self,
        task_id: int,
        refusal_reason: str,
    ) -> TicketTaskSnapshot | None:
        normalized_reason = str(refusal_reason or "").strip()
        if not normalized_reason:
            return None
        current_snapshot = self._state.get_task(task_id)
        current_task = (
            TicketTask.from_snapshot(current_snapshot)
            if current_snapshot is not None
            else None
        )
        result = self._state_service.mark_task_as_refused(
            task_id,
            current_task,
            normalized_reason,
        )
        if result.task is None:
            return None
        self._assign_sequence_on_terminal_state(result.task)
        snapshot = result.task.to_snapshot()
        self._state.upsert_task(snapshot)
        self._auto_save_archive_record(snapshot)
        return self._sync_gateway_state(snapshot)

    def create_diagnostic_report(
        self, task_id: int, initial_cause: str, actual_cause: str,
    ) -> TicketDocumentSnapshot:
        return self._document_service.create_diagnostic_report(
            task_id, initial_cause, actual_cause,
        )

    def create_repair_report(
        self, task_id: int, work_done: str, used_parts: str, recommendations: str,
    ) -> TicketDocumentSnapshot:
        return self._document_service.create_repair_report(
            task_id, work_done, used_parts, recommendations,
        )

    def create_acceptance_report(
        self, task_id: int, work_description: str,
        executor_signature: str, customer_signature: str,
    ) -> TicketDocumentSnapshot:
        return self._document_service.create_acceptance_report(
            task_id, work_description, executor_signature, customer_signature,
        )

    def list_documents(
        self,
        document_type: str | None = None,
    ) -> list[TicketDocumentSnapshot]:
        return self._document_service.list_documents(document_type)

    def can_advance_to_confirmation(self, task_id: int) -> bool:
        return self._report_service.can_advance_to_confirmation(task_id)

    def can_advance_to_completed(self, task_id: int) -> bool:
        return self._report_service.can_advance_to_completed(task_id)

    def get_active_view(self) -> str:
        return self._state.active_view

    def get_gateway_status(self) -> TicketHardwareStatus:
        return self._hardware_gateway.get_status()

    def on_task_action(self, raw_action: Mapping[str, Any]) -> None:
        """Observer callback от hardware gateway."""
        try:
            self.handle_task_action(raw_action)
        except Exception as exc:
            log_exception(__name__, "TaskApplicationService.on_task_action", exc)

    def on_gateway_status(self, status: TicketHardwareStatus) -> None:
        """Observer callback обновления статуса hardware gateway."""
        if status.connection_status == TicketConnectionStatus.ERROR:
            self._state.set_error(status.message)
        else:
            self._state.connection_status = status.connection_status
        self._state.set_com_connection(
            status.connection_status == TicketConnectionStatus.CONNECTED,
            status.message,
        )
        self._state.set_button_initialization(
            status.buttons_initialized,
            status.button_count,
        )

    def on_gateway_error(self, message: str) -> None:
        """Observer callback ошибки hardware gateway."""
        self._state.set_error(message)

    def _connect_state_signals(self) -> None:
        self._state.task_updated.connect(self.task_updated.emit)
        self._state.task_removed.connect(self.task_removed.emit)
        self._state.connection_changed.connect(self.connection_changed.emit)
        self._state.active_view_changed.connect(self.active_view_changed.emit)
        self._state.state_loaded.connect(self.state_loaded.emit)
        self._state.com_connection_changed.connect(self.com_connection_changed.emit)
        self._state.button_initialization_changed.connect(
            self.button_initialization_changed.emit
        )

    def _sync_gateway_state(
        self,
        snapshot: TicketTaskSnapshot | None,
    ) -> TicketTaskSnapshot | None:
        if snapshot is None:
            return None
        self._hardware_gateway.set_button_state(snapshot.task_id, snapshot.state_code)
        return snapshot

    def _assign_sequence_on_terminal_state(self, task: TicketTask) -> None:
        """Присвоить сквозной номер при переходе в Выполненные/Отказ."""
        if task.state_code in {STATE_COMPLETED, STATE_REFUSED}:
            task.sequence_number = self._state.next_sequence_number()

    def _auto_save_archive_record(self, snapshot: TicketTaskSnapshot) -> None:
        if snapshot.state_code in {STATE_COMPLETED, STATE_REFUSED}:
            self._archive_service.ensure_archive_record(snapshot)

    @staticmethod
    def _normalize_int(value: Any) -> int | None:
        try:
            return int(value)
        except (TypeError, ValueError):
            return None
