"""Gateway runtime version compatibility checks."""

from __future__ import annotations

import re
from dataclasses import dataclass
from typing import Any

from app.core.config import settings
from app.services.openclaw.gateway_rpc import GatewayConfig, OpenClawGatewayError, openclaw_call

_VERSION_PATTERN = re.compile(r"(?i)v?(?P<version>\d+(?:\.\d+)+)")
_PRIMARY_VERSION_PATHS: tuple[tuple[str, ...], ...] = (
    ("version",),
    ("gatewayVersion",),
    ("appVersion",),
    ("buildVersion",),
    ("gateway", "version"),
    ("app", "version"),
    ("server", "version"),
    ("runtime", "version"),
    ("meta", "version"),
    ("build", "version"),
    ("info", "version"),
)


@dataclass(frozen=True, slots=True)
class GatewayVersionCheckResult:
    """Compatibility verdict for a gateway runtime version."""

    compatible: bool
    minimum_version: str
    current_version: str | None
    message: str | None = None


def _normalized_minimum_version() -> str:
    raw = (settings.gateway_min_version or "").strip()
    return raw or "2026.1.30"


def _parse_version_parts(value: str) -> tuple[int, ...] | None:
    match = _VERSION_PATTERN.search(value.strip())
    if match is None:
        return None
    numeric = match.group("version")
    return tuple(int(part) for part in numeric.split("."))


def _compare_versions(left: tuple[int, ...], right: tuple[int, ...]) -> int:
    width = max(len(left), len(right))
    left_padded = left + (0,) * (width - len(left))
    right_padded = right + (0,) * (width - len(right))
    if left_padded < right_padded:
        return -1
    if left_padded > right_padded:
        return 1
    return 0


def _value_at_path(payload: object, path: tuple[str, ...]) -> object | None:
    current = payload
    for segment in path:
        if not isinstance(current, dict):
            return None
        if segment not in current:
            return None
        current = current[segment]
    return current


def _coerce_version_string(value: object) -> str | None:
    if isinstance(value, str):
        normalized = value.strip()
        return normalized or None
    if isinstance(value, (int, float)):
        return str(value)
    return None


def _iter_fallback_version_values(payload: object) -> list[str]:
    if not isinstance(payload, dict):
        return []
    stack: list[dict[str, Any]] = [payload]
    discovered: list[str] = []
    while stack:
        node = stack.pop()
        for key, value in node.items():
            if isinstance(value, dict):
                stack.append(value)
            key_lower = key.lower()
            if "version" not in key_lower or "protocol" in key_lower:
                continue
            candidate = _coerce_version_string(value)
            if candidate is not None:
                discovered.append(candidate)
    return discovered


def extract_gateway_version(payload: object) -> str | None:
    """Extract a gateway runtime version string from status/health payloads."""
    for path in _PRIMARY_VERSION_PATHS:
        candidate = _coerce_version_string(_value_at_path(payload, path))
        if candidate is not None:
            return candidate

    for candidate in _iter_fallback_version_values(payload):
        if _parse_version_parts(candidate) is not None:
            return candidate
    return None


def evaluate_gateway_version(
    *,
    current_version: str | None,
    minimum_version: str | None = None,
) -> GatewayVersionCheckResult:
    """Return compatibility result for the reported gateway version."""
    min_version = (minimum_version or _normalized_minimum_version()).strip()
    min_parts = _parse_version_parts(min_version)
    if min_parts is None:
        msg = (
            "Server configuration error: GATEWAY_MIN_VERSION is invalid. "
            f"Expected a dotted numeric version, got '{min_version}'."
        )
        return GatewayVersionCheckResult(
            compatible=False,
            minimum_version=min_version,
            current_version=current_version,
            message=msg,
        )

    if current_version is None:
        return GatewayVersionCheckResult(
            compatible=False,
            minimum_version=min_version,
            current_version=None,
            message=(
                "Unable to determine gateway version from runtime metadata. "
                f"Minimum supported version is {min_version}."
            ),
        )

    current_parts = _parse_version_parts(current_version)
    if current_parts is None:
        return GatewayVersionCheckResult(
            compatible=False,
            minimum_version=min_version,
            current_version=current_version,
            message=(
                f"Gateway reported an unsupported version format '{current_version}'. "
                f"Minimum supported version is {min_version}."
            ),
        )

    if _compare_versions(current_parts, min_parts) < 0:
        return GatewayVersionCheckResult(
            compatible=False,
            minimum_version=min_version,
            current_version=current_version,
            message=(
                f"Gateway version {current_version} is not supported. "
                f"Minimum supported version is {min_version}."
            ),
        )

    return GatewayVersionCheckResult(
        compatible=True,
        minimum_version=min_version,
        current_version=current_version,
    )


async def _fetch_runtime_metadata(config: GatewayConfig) -> object:
    last_error: OpenClawGatewayError | None = None
    for method in ("status", "health"):
        try:
            return await openclaw_call(method, config=config)
        except OpenClawGatewayError as exc:
            last_error = exc
            continue
    if last_error is not None:
        raise last_error
    return {}


async def _fetch_schema_metadata(config: GatewayConfig) -> object | None:
    try:
        return await openclaw_call("config.schema", config=config)
    except OpenClawGatewayError:
        return None


async def check_gateway_runtime_compatibility(
    config: GatewayConfig,
    *,
    minimum_version: str | None = None,
) -> GatewayVersionCheckResult:
    """Fetch runtime metadata and evaluate gateway version compatibility."""
    schema_payload = await _fetch_schema_metadata(config)
    current_version = extract_gateway_version(schema_payload)
    if current_version is not None:
        return evaluate_gateway_version(
            current_version=current_version,
            minimum_version=minimum_version,
        )

    payload = await _fetch_runtime_metadata(config)
    current_version = extract_gateway_version(payload)
    if current_version is None:
        try:
            health_payload = await openclaw_call("health", config=config)
        except OpenClawGatewayError:
            health_payload = None
        if health_payload is not None:
            current_version = extract_gateway_version(health_payload)
    return evaluate_gateway_version(
        current_version=current_version,
        minimum_version=minimum_version,
    )
