# -*- coding: utf-8 -*-
# hub/ticket/state/runtime_state.py

"""Runtime-state Ticket с Qt-сигналами и единым путём сохранения."""

from __future__ import annotations

from datetime import datetime
from typing import Any, Mapping

from PySide6.QtCore import QObject, Signal

from error_logger import log_exception

from domain import TicketConnectionStatus, TicketTaskSnapshot
from domain.task import TicketTask, _normalize_color, _normalize_datetime, _normalize_int
from .paths import TASKS_FILE
from .repository import TicketStateRepository


class TicketRuntimeState(QObject):
    """Канонический runtime-state 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, repository: TicketStateRepository | None = None):
        super().__init__()
        self._repository = repository or TicketStateRepository(TASKS_FILE)
        self._tasks: dict[int, TicketTask] = {}
        self._connection_status = TicketConnectionStatus.DISCONNECTED
        self._active_view = "board"
        self._com_connected = False
        self._buttons_initialized = False
        self._button_count = 0
        self._sequence_counter = 0

    def load(self) -> None:
        """Загрузить задачи из репозитория в память."""
        self._tasks = {task.task_id: task for task in self._repository.load_tasks()}
        self._sequence_counter = max(
            (t.sequence_number for t in self._tasks.values()), default=0,
        )
        self.state_loaded.emit()
        for task in self.list_tasks():
            self.task_updated.emit(task)

    def list_tasks(self) -> list[TicketTaskSnapshot]:
        """Вернуть снимки всех активных и архивных задач."""
        return [task.to_snapshot() for task in self._tasks.values()]

    def list_active_tasks(self) -> list[TicketTaskSnapshot]:
        """Вернуть снимки только неархивных задач."""
        return [task.to_snapshot() for task in self._tasks.values() if task.state_code != 5]

    def list_archived_tasks(self) -> list[TicketTaskSnapshot]:
        """Вернуть снимки архивных задач."""
        return [task.to_snapshot() for task in self._tasks.values() if task.state_code == 5]

    def get_task(self, task_id: int) -> TicketTaskSnapshot | None:
        """Вернуть снимок задачи по идентификатору."""
        task = self._tasks.get(task_id)
        return task.to_snapshot() if task else None

    def next_sequence_number(self) -> int:
        """Вернуть следующий сквозной номер задачи."""
        self._sequence_counter += 1
        return self._sequence_counter

    def upsert_task(self, task: TicketTaskSnapshot) -> None:
        """Создать или обновить задачу по готовому snapshot."""
        self._tasks[task.task_id] = TicketTask.from_snapshot(task)
        self._save_and_emit(task.task_id)

    def update_task(self, task_data: Mapping[str, Any]) -> TicketTaskSnapshot | None:
        """Обновить задачу по legacy-совместимому словарю полей."""
        raw_task_id = task_data.get("button_id")
        if raw_task_id is None:
            return None
        try:
            task_id = int(raw_task_id)
        except (TypeError, ValueError):
            return None

        task = self._tasks.get(task_id)
        if task is None:
            created_task = TicketTask.from_record(task_data)
            if created_task is None:
                return None
            task = created_task
            task.created_at = task.created_at or datetime.now()
            self._tasks[task_id] = task
        else:
            self._apply_update(task, task_data)

        self._save_and_emit(task_id)
        return self.get_task(task_id)

    def remove_task(self, task_id: int) -> None:
        """Удалить задачу из runtime-state и хранилища."""
        if task_id not in self._tasks:
            return
        del self._tasks[task_id]
        self._persist()
        self.task_removed.emit(task_id)

    def can_advance_to_confirmation(self, task_id: int) -> bool:
        """Проверить, что обязательные отчёты подписаны."""
        task = self._tasks.get(task_id)
        if task is None:
            return False
        return task.diagnostic_report_signed and task.repair_report_signed

    def sign_report(self, task_id: int, report_type: str) -> None:
        """Отметить подписание диагностического или ремонтного отчёта."""
        task = self._tasks.get(task_id)
        if task is None:
            return
        if report_type == "diagnostic":
            task.diagnostic_report_signed = True
        elif report_type == "repair":
            task.repair_report_signed = True
        else:
            return
        self._save_and_emit(task_id)

    def set_error(self, message: str) -> None:
        """Перевести статус подключения в ошибку и уведомить подписчиков."""
        self._connection_status = TicketConnectionStatus.ERROR
        self.connection_changed.emit(self._connection_status.value, message)

    @property
    def connection_status(self) -> TicketConnectionStatus:
        return self._connection_status

    @connection_status.setter
    def connection_status(self, value: TicketConnectionStatus) -> None:
        if self._connection_status == value:
            return
        self._connection_status = value
        self.connection_changed.emit(value.value, "")

    @property
    def active_view(self) -> str:
        return self._active_view

    @active_view.setter
    def active_view(self, value: str) -> None:
        if self._active_view == value:
            return
        self._active_view = value
        self.active_view_changed.emit(value)

    @property
    def com_connected(self) -> bool:
        return self._com_connected

    @com_connected.setter
    def com_connected(self, value: bool) -> None:
        if self._com_connected == value:
            return
        self._com_connected = value
        self.com_connection_changed.emit(value, "")

    @property
    def buttons_initialized(self) -> bool:
        return self._buttons_initialized

    @buttons_initialized.setter
    def buttons_initialized(self, value: bool) -> None:
        if self._buttons_initialized == value:
            return
        self._buttons_initialized = value
        self.button_initialization_changed.emit(value, self._button_count)

    def set_button_initialization(self, is_initialized: bool, button_count: int) -> None:
        """Единая точка обновления статуса инициализации кнопок."""
        self._buttons_initialized = is_initialized
        self._button_count = button_count
        self.button_initialization_changed.emit(is_initialized, button_count)

    def set_com_connection(self, is_connected: bool, message: str) -> None:
        """Единая точка обновления статуса COM-подключения."""
        self._com_connected = is_connected
        self.com_connection_changed.emit(is_connected, message)

    def _apply_update(self, task: TicketTask, task_data: Mapping[str, Any]) -> None:
        """Применить обновление к уже существующей задаче."""
        new_state = task_data.get("state")
        if new_state is not None:
            try:
                normalized_state = int(new_state)
            except (TypeError, ValueError):
                normalized_state = task.state_code
        else:
            normalized_state = task.state_code

        if normalized_state == 4 and task.state_code != 4:
            task.refused_from_state = task.state_code
        elif normalized_state != 4 and "location" in task_data:
            task.location = str(task_data.get("location", task.location))

        task.state_code = normalized_state
        if "action" in task_data:
            task.action_text = str(task_data.get("action", task.action_text))
        if "state_name" in task_data:
            task.state_name = str(task_data.get("state_name", task.state_name))
        if "color" in task_data:
            task.color_hex = _normalize_color(task_data.get("color"))
        if "created_time" in task_data:
            task.created_at = _normalize_datetime(task_data.get("created_time")) or task.created_at
        if "completed_time" in task_data:
            task.completed_at = _normalize_datetime(task_data.get("completed_time"))
        if "refused_from_state" in task_data:
            task.refused_from_state = _normalize_int(
                task_data.get("refused_from_state"),
                task.refused_from_state,
            )
        if "refusal_reason" in task_data and task_data.get("refusal_reason") is not None:
            task.refusal_reason = str(task_data.get("refusal_reason", "")).strip()
        if "assigned_specialist" in task_data and task_data.get("assigned_specialist") is not None:
            task.assigned_specialist = str(task_data.get("assigned_specialist", ""))
        if "specialist_photo" in task_data and task_data.get("specialist_photo") is not None:
            task.specialist_photo = str(task_data.get("specialist_photo", ""))
        if "diagnostic_report_signed" in task_data and task_data.get("diagnostic_report_signed") is not None:
            task.diagnostic_report_signed = bool(task_data.get("diagnostic_report_signed"))
        if "repair_report_signed" in task_data and task_data.get("repair_report_signed") is not None:
            task.repair_report_signed = bool(task_data.get("repair_report_signed"))
        if "acceptance_report_signed" in task_data and task_data.get("acceptance_report_signed") is not None:
            task.acceptance_report_signed = bool(task_data.get("acceptance_report_signed"))
        if normalized_state == 0 and task.completed_at is None:
            task.completed_at = datetime.now()

    def _save_and_emit(self, task_id: int) -> None:
        """Сохранить состояние и уведомить подписчиков о задаче."""
        self._persist()
        snapshot = self.get_task(task_id)
        if snapshot is not None:
            self.task_updated.emit(snapshot)

    def _persist(self) -> None:
        """Сохранить весь state в репозиторий."""
        try:
            self._repository.save_tasks(list(self._tasks.values()))
        except Exception as exc:
            log_exception(__name__, "TicketRuntimeState._persist", exc)
