"""统一UI字典的启动期加载、严格校验与只读序列化。"""

from __future__ import annotations

import json
import re
from dataclasses import dataclass
from enum import StrEnum
from pathlib import Path
from types import MappingProxyType
from typing import Any, Mapping

from dms.common.enums import (
    AllowedAction,
    AllowedModule,
    AttachmentType,
    AuditAction,
    AuditResult,
    AuditTarget,
    CategoryType,
    DocumentStatus,
    DocumentType,
    EnabledStatus,
    RoleCode,
    SecurityLevel,
    SubjectType,
    VisibilityType,
)


DICTIONARY_ENUMS: Mapping[str, type[StrEnum]] = MappingProxyType(
    {
        "roleCodes": RoleCode,
        "allowedModules": AllowedModule,
        "documentTypes": DocumentType,
        "documentStatuses": DocumentStatus,
        "securityLevels": SecurityLevel,
        "visibilityTypes": VisibilityType,
        "attachmentTypes": AttachmentType,
        "subjectTypes": SubjectType,
        "allowedActions": AllowedAction,
        "categoryTypes": CategoryType,
        "enabledStatuses": EnabledStatus,
        "auditResults": AuditResult,
        "auditActions": AuditAction,
        "auditTargets": AuditTarget,
    }
)

_VERSION_PATTERN = re.compile(r"^[0-9]{4}\.[0-9]+$")
_LOCALE_PATTERN = re.compile(r"^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$")


class UiDictionaryConfigError(RuntimeError):
    """不包含文件路径或原始内容的UI字典配置错误。"""


@dataclass(frozen=True, slots=True)
class DictionaryOption:
    code: str
    label: str
    sort_no: int

    def to_dict(self) -> dict[str, str | int]:
        return {
            "code": self.code,
            "label": self.label,
            "sortNo": self.sort_no,
        }


@dataclass(frozen=True, slots=True)
class UiDictionaryConfig:
    version: str
    locale: str
    dictionaries: Mapping[str, tuple[DictionaryOption, ...]]

    def to_dict(self) -> dict[str, Any]:
        return {
            "version": self.version,
            "locale": self.locale,
            "dictionaries": {
                name: [option.to_dict() for option in options]
                for name, options in self.dictionaries.items()
            },
        }


def _invalid(reason: str) -> UiDictionaryConfigError:
    return UiDictionaryConfigError(f"UI字典配置无效：{reason}")


def _read_json(config_path: Path) -> Any:
    try:
        with config_path.open("r", encoding="utf-8") as source:
            return json.load(source)
    except FileNotFoundError:
        raise UiDictionaryConfigError("UI字典配置文件不存在") from None
    except UnicodeDecodeError:
        raise UiDictionaryConfigError(
            "UI字典配置不是有效的UTF-8文件"
        ) from None
    except json.JSONDecodeError:
        raise UiDictionaryConfigError("UI字典配置不是有效的JSON") from None
    except OSError:
        raise UiDictionaryConfigError("UI字典配置文件无法读取") from None


def _validate_option(
    dictionary_name: str,
    raw_option: Any,
    seen_codes: set[str],
) -> DictionaryOption:
    if not isinstance(raw_option, dict):
        raise _invalid(f"{dictionary_name}的字典项必须是对象")
    if set(raw_option) != {"code", "label", "sortNo"}:
        raise _invalid(f"{dictionary_name}的字典项字段不合法")

    code = raw_option["code"]
    label = raw_option["label"]
    sort_no = raw_option["sortNo"]
    if not isinstance(code, str) or not code:
        raise _invalid(f"{dictionary_name}包含非法code")
    if code in seen_codes:
        raise _invalid(f"{dictionary_name}包含重复code")
    if not isinstance(label, str) or not label.strip():
        raise _invalid(f"{dictionary_name}包含空label")
    if type(sort_no) is not int or sort_no < 0:
        raise _invalid(f"{dictionary_name}包含非法sortNo")

    seen_codes.add(code)
    return DictionaryOption(code=code, label=label.strip(), sort_no=sort_no)


def load_ui_dictionary_config(
    config_path: str | Path,
) -> UiDictionaryConfig:
    """读取并严格校验一份完整UI字典配置。"""
    raw_config = _read_json(Path(config_path))
    if not isinstance(raw_config, dict):
        raise _invalid("根节点必须是对象")
    if set(raw_config) != {"version", "locale", "dictionaries"}:
        raise _invalid("根节点字段不完整或包含未知字段")

    version = raw_config["version"]
    locale = raw_config["locale"]
    raw_dictionaries = raw_config["dictionaries"]
    if not isinstance(version, str) or not _VERSION_PATTERN.fullmatch(version):
        raise _invalid("version格式不合法")
    if not isinstance(locale, str) or not _LOCALE_PATTERN.fullmatch(locale):
        raise _invalid("locale格式不合法")
    if not isinstance(raw_dictionaries, dict):
        raise _invalid("dictionaries必须是对象")

    expected_names = set(DICTIONARY_ENUMS)
    actual_names = set(raw_dictionaries)
    if actual_names != expected_names:
        raise _invalid("dictionary名称不完整或包含未知名称")

    validated: dict[str, tuple[DictionaryOption, ...]] = {}
    for dictionary_name, enum_type in DICTIONARY_ENUMS.items():
        raw_options = raw_dictionaries[dictionary_name]
        if not isinstance(raw_options, list):
            raise _invalid(f"{dictionary_name}必须是数组")

        seen_codes: set[str] = set()
        options = tuple(
            _validate_option(dictionary_name, option, seen_codes)
            for option in raw_options
        )
        expected_codes = {member.value for member in enum_type}
        if seen_codes != expected_codes:
            raise _invalid(f"{dictionary_name}的code集合与服务端枚举不一致")
        validated[dictionary_name] = tuple(
            sorted(options, key=lambda option: (option.sort_no, option.code))
        )

    return UiDictionaryConfig(
        version=version,
        locale=locale,
        dictionaries=MappingProxyType(validated),
    )


__all__ = [
    "DICTIONARY_ENUMS",
    "DictionaryOption",
    "UiDictionaryConfig",
    "UiDictionaryConfigError",
    "load_ui_dictionary_config",
]
