# SPDX-FileCopyrightText: 2025 XLeitstelle Planen und Bauen <xleitstelle@gv.hamburg.de>
# SPDX-FileContributor: Anton Jacobsson <anton.jacobsson@init.de>
# SPDX-FileContributor: Tobias Kraft <tobias.kraft@gv.hamburg.de>
#
# SPDX-License-Identifier: EUPL-1.2

from __future__ import annotations

import json
import logging
import re
import uuid
from dataclasses import dataclass
from typing import Literal, Optional, get_args

from packaging.version import Version
from qgis.core import QgsVectorLayer

from xmas_plugin.settings_manager import APPSCHEMAS, LAYER_TYPES
from xmas_plugin.util.metadata import PLUGIN_DIR_NAME, PLUGIN_VERSION

logger = logging.getLogger(PLUGIN_DIR_NAME)


@dataclass(frozen=True)
class ValidationIssue:
    key: str
    msg: str
    severity: Literal["error", "warning"]


@dataclass(frozen=True)
class LayerValidationResult:
    is_plugin_layer: bool  # has xmas_plugin/* property
    ok: bool  # only meaningful if is_plugin_layer=True
    issues: list[ValidationIssue]


_PLUGIN_PREFIX = f"{PLUGIN_DIR_NAME}/"


def _is_uuid(val: str) -> bool:
    try:
        uuid.UUID(str(val))
        return True
    except Exception:
        return False


def _regex_compiles(val: str) -> bool:
    try:
        re.compile(str(val))
        return True
    except re.error:
        return False


def validate_custom_properties(layer: QgsVectorLayer) -> LayerValidationResult:
    """Validates xmas_plugin/* custom properties on a QgsMapLayer.

    Required:
    - xmas_plugin/appschema in APPSCHEMAS appschema keys
    - xmas_plugin/appschema_version in APPSCHEMAS appschema versions
    - xmas_plugin/layer_type in allowed_layer_types
    - xmas_plugin/plan_id present and valid UUID
    - xmas_plugin/plugin_version major and minor version correspond to current plugin version

    Optional but validated if present:
    - xmas_plugin/featuretype_regex: must compile
    - xmas_plugin/bereich_id: valid UUID
    - xmas_plugin/property_filter: valid JSON object
    - plugin_version >= min_required_version (if both provided)
    """
    allowed_layer_types = get_args(
        LAYER_TYPES
    )  # -> ("plan","text","section","presentation","subject")
    props = {
        key: layer.customProperty(key, None) for key in layer.customProperties().keys()
    }
    keys = list(props.keys())
    is_plugin_layer = any(k.startswith(_PLUGIN_PREFIX) for k in keys)

    if not is_plugin_layer:
        # tree-walker decides how to flag.
        return LayerValidationResult(False, False, [])

    issues: list[ValidationIssue] = []

    def _req(key: str) -> Optional[str]:
        fq = f"{_PLUGIN_PREFIX}{key}"
        v = props.get(fq)
        if v in (None, ""):
            issues.append(
                ValidationIssue(key, f"Pflicht-Property '{fq}' fehlt.", "error")
            )
            return None
        return str(v)

    # Required
    plugin_version = _req("plugin_version")
    appschema = _req("appschema")
    layer_type = _req("layer_type")
    appschema_version = _req("appschema_version")
    plan_id = _req("plan_id")

    featuretype_regex = props.get(f"{_PLUGIN_PREFIX}featuretype_regex")
    bereich_id = props.get(f"{_PLUGIN_PREFIX}bereich_id")
    property_filter = props.get(f"{_PLUGIN_PREFIX}property_filter")

    appschema_dict = {value["type"]: value["versions"] for value in APPSCHEMAS.values()}

    if appschema and appschema_version:
        if appschema not in (keys := appschema_dict.keys()):
            issues.append(
                ValidationIssue(
                    "appschema",
                    f"Unerwartetes appschema '{appschema}' (erlaubt: {sorted(set(keys))}).",
                    "error",
                )
            )
        elif appschema_version not in (versions := appschema_dict[appschema]):
            issues.append(
                ValidationIssue(
                    "appschema_version",
                    f"Unerwartete version '{appschema_version}' für appschema '{appschema}' (erlaubt: {sorted(set(versions))}).",
                    "error",
                )
            )

    if plugin_version:
        try:
            current_version = Version(PLUGIN_VERSION)
            layer_version = Version(plugin_version)
            if not (current_version.major == layer_version.major) and (
                current_version.minor == layer_version.minor
            ):
                issues.append(
                    ValidationIssue(
                        "plugin_version",
                        f"Plugin-Version {PLUGIN_VERSION} nicht kompatibel mit Layer-Version {plugin_version}, bitte neu erstellen.",
                        "error",
                    )
                )
        except Exception:
            issues.append(
                ValidationIssue(
                    "plugin_version", "Versionsprüfung fehlgeschlagen.", "error"
                )
            )

    if layer_type and layer_type not in set(allowed_layer_types):
        issues.append(
            ValidationIssue(
                "layer_type",
                f"Ungültiger layer_type '{layer_type}' (erlaubt: {sorted(set(allowed_layer_types))}).",
                "error",
            )
        )

    if featuretype_regex and not _regex_compiles(featuretype_regex):
        issues.append(
            ValidationIssue("featuretype_regex", "Kein gültiger Regex.", "error")
        )

    if plan_id and not _is_uuid(plan_id):
        issues.append(ValidationIssue("plan_id", "Keine gültige UUID.", "error"))

    if bereich_id and not _is_uuid(bereich_id):
        issues.append(ValidationIssue("bereich_id", "Keine gültige UUID.", "error"))

    if property_filter:
        try:
            parsed = json.loads(str(property_filter))
            if not isinstance(parsed, dict):
                issues.append(
                    ValidationIssue(
                        "property_filter", "Muss ein JSON-Objekt sein.", "error"
                    )
                )
        except json.JSONDecodeError:
            issues.append(
                ValidationIssue("property_filter", "Ungültiges JSON.", "error")
            )

    ok = not any(i.severity == "error" for i in issues)
    if issues:
        logger.warning(
            "Found %s issues while validating layer %s", len(issues), layer.name()
        )
    return LayerValidationResult(True, ok, issues)
