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

"""Файловый репозиторий архивных записей Ticket."""

from __future__ import annotations

import json
from datetime import datetime
from pathlib import Path
from typing import Any

from error_logger import log_exception

from domain import ArchiveRecordSnapshot, TicketDocumentSnapshot, TicketTaskSnapshot
from .paths import ARCHIVE_DIR, ensure_storage_directories


class ArchiveRecordRepository:
    """Каноническое файловое хранилище архивных записей Ticket."""

    def __init__(self, archive_dir: Path = ARCHIVE_DIR):
        self._archive_dir = archive_dir
        ensure_storage_directories()
        self._archive_dir.mkdir(parents=True, exist_ok=True)

    def has_record(self, task_id: int, cycle_token: str = "") -> bool:
        """Проверить, существует ли запись для задачи (и цикла)."""
        if not cycle_token:
            pattern = f"archive_task_{task_id}_*.json"
            return any(self._archive_dir.glob(pattern))
        for path in self._archive_dir.glob(f"archive_task_{task_id}_*.json"):
            record = self._load_record(path)
            if record is None:
                continue
            if record.created_at is not None:
                token = record.created_at.strftime("%Y%m%d_%H%M%S")
                if token == cycle_token:
                    return True
        return False

    def save_record(
        self,
        task: TicketTaskSnapshot,
        documents: list[TicketDocumentSnapshot],
    ) -> ArchiveRecordSnapshot | None:
        """Сохранить архивную запись для завершённой/отказной задачи."""
        now = datetime.now()
        pre_state_code = self._resolve_pre_archive_state(task)
        pre_state_name = self._resolve_pre_archive_state_name(task, pre_state_code)
        document_ids = tuple(doc.document_id for doc in documents)

        record = ArchiveRecordSnapshot(
            task_id=task.task_id,
            location=task.location,
            pre_archive_state_code=pre_state_code,
            pre_archive_state_name=pre_state_name,
            color_hex=task.color_hex,
            created_at=task.created_at,
            completed_at=task.completed_at,
            archived_at=now,
            refused_from_state=task.refused_from_state,
            refusal_reason=task.refusal_reason,
            assigned_specialist=task.assigned_specialist,
            specialist_photo=task.specialist_photo,
            diagnostic_report_signed=task.diagnostic_report_signed,
            repair_report_signed=task.repair_report_signed,
            acceptance_report_signed=task.acceptance_report_signed,
            document_ids=document_ids,
            action_text=task.action_text,
            sequence_number=task.sequence_number,
        )

        file_path = self._record_path(task.task_id, now)
        payload = self._serialize(record)
        try:
            with open(file_path, "w", encoding="utf-8") as handle:
                json.dump(payload, handle, indent=2, ensure_ascii=False)
        except Exception as exc:
            log_exception(__name__, "ArchiveRecordRepository.save_record", exc)
            return None
        return record

    def list_records(self) -> list[ArchiveRecordSnapshot]:
        """Загрузить все архивные записи из каталога."""
        records: list[ArchiveRecordSnapshot] = []
        for path in self._archive_dir.glob("*.json"):
            record = self._load_record(path)
            if record is not None:
                records.append(record)
        records.sort(key=lambda r: r.archived_at or datetime.min, reverse=True)
        return records

    def _load_record(self, path: Path) -> ArchiveRecordSnapshot | None:
        try:
            with open(path, "r", encoding="utf-8") as handle:
                data = json.load(handle)
        except Exception as exc:
            log_exception(__name__, "ArchiveRecordRepository._load_record", exc)
            return None
        if not isinstance(data, dict):
            return None
        return self._deserialize(data)

    def _record_path(self, task_id: int, archived_at: datetime) -> Path:
        stamp = archived_at.strftime("%Y%m%d_%H%M%S")
        return self._archive_dir / f"archive_task_{task_id}_{stamp}.json"

    @staticmethod
    def _resolve_pre_archive_state(task: TicketTaskSnapshot) -> int:
        from ..domain.ticket_constants import STATE_COMPLETED, STATE_REFUSED
        if task.state_code == STATE_COMPLETED:
            return STATE_COMPLETED
        if task.state_code == STATE_REFUSED:
            return STATE_REFUSED
        if task.refused_from_state is not None:
            return STATE_REFUSED
        return STATE_COMPLETED

    @staticmethod
    def _resolve_pre_archive_state_name(task: TicketTaskSnapshot, state_code: int) -> str:
        from ..domain.ticket_constants import STATE_REFUSED
        if state_code == STATE_REFUSED:
            return "Отказ в обслуживании"
        return "Выполненные"

    @staticmethod
    def _serialize(record: ArchiveRecordSnapshot) -> dict[str, Any]:
        return {
            "task_id": record.task_id,
            "location": record.location,
            "pre_archive_state_code": record.pre_archive_state_code,
            "pre_archive_state_name": record.pre_archive_state_name,
            "color_hex": record.color_hex,
            "created_at": record.created_at.isoformat() if record.created_at else None,
            "completed_at": record.completed_at.isoformat() if record.completed_at else None,
            "archived_at": record.archived_at.isoformat() if record.archived_at else None,
            "refused_from_state": record.refused_from_state,
            "refusal_reason": record.refusal_reason,
            "assigned_specialist": record.assigned_specialist,
            "specialist_photo": record.specialist_photo,
            "diagnostic_report_signed": record.diagnostic_report_signed,
            "repair_report_signed": record.repair_report_signed,
            "acceptance_report_signed": record.acceptance_report_signed,
            "document_ids": list(record.document_ids),
            "action_text": record.action_text,
            "sequence_number": record.sequence_number,
        }

    @staticmethod
    def _deserialize(data: dict[str, Any]) -> ArchiveRecordSnapshot | None:
        try:
            task_id = int(data["task_id"])
        except (KeyError, TypeError, ValueError):
            return None
        return ArchiveRecordSnapshot(
            task_id=task_id,
            location=str(data.get("location", "")),
            pre_archive_state_code=int(data.get("pre_archive_state_code", 0)),
            pre_archive_state_name=str(data.get("pre_archive_state_name", "")),
            color_hex=str(data.get("color_hex", "#FFFFFF")),
            created_at=_parse_datetime(data.get("created_at")),
            completed_at=_parse_datetime(data.get("completed_at")),
            archived_at=_parse_datetime(data.get("archived_at")),
            refused_from_state=_parse_optional_int(data.get("refused_from_state")),
            refusal_reason=str(data.get("refusal_reason", "")),
            assigned_specialist=str(data.get("assigned_specialist", "")),
            specialist_photo=str(data.get("specialist_photo", "")),
            diagnostic_report_signed=bool(data.get("diagnostic_report_signed")),
            repair_report_signed=bool(data.get("repair_report_signed")),
            acceptance_report_signed=bool(data.get("acceptance_report_signed")),
            document_ids=tuple(data.get("document_ids", ())),
            action_text=str(data.get("action_text", "")),
            sequence_number=int(data.get("sequence_number", 0) or 0),
        )


def _parse_datetime(value: Any) -> datetime | None:
    if value is None:
        return None
    if isinstance(value, str):
        try:
            return datetime.fromisoformat(value)
        except ValueError:
            return None
    return None


def _parse_optional_int(value: Any) -> int | None:
    if value is None:
        return None
    try:
        return int(value)
    except (TypeError, ValueError):
        return None
