from __future__ import annotations

import json
import os
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from g_gremlin.scout.models import ScoutDefinition


def _utc_now_iso_z() -> str:
    return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")


class ScoutConfigStore:
    def __init__(self, state_dir: Path) -> None:
        self.state_dir = Path(state_dir)
        self.path = self.state_dir / "config" / "scout_overrides.json"

    def load_all(self) -> dict[str, dict[str, Any]]:
        if not self.path.exists():
            return {}
        try:
            payload = json.loads(self.path.read_text(encoding="utf-8"))
        except Exception:
            return {}
        if not isinstance(payload, dict):
            return {}
        overrides = payload.get("overrides", {})
        if not isinstance(overrides, dict):
            return {}

        cleaned: dict[str, dict[str, Any]] = {}
        for scout_name, params in overrides.items():
            if not isinstance(scout_name, str):
                continue
            if not isinstance(params, dict):
                continue
            cleaned[scout_name] = dict(params)
        return cleaned

    def load_scout(self, scout_name: str) -> dict[str, Any]:
        return dict(self.load_all().get(scout_name, {}))

    def save_all(self, overrides: dict[str, dict[str, Any]], *, actor: str = "api") -> None:
        self.path.parent.mkdir(parents=True, exist_ok=True)
        payload = {
            "$schema": "ScoutOverrides/v1",
            "overrides": overrides,
            "updated_at": _utc_now_iso_z(),
            "updated_by": actor,
        }
        tmp_path = self.path.with_suffix(".json.tmp")
        tmp_path.write_text(json.dumps(payload, indent=2, sort_keys=True), encoding="utf-8")
        for attempt in range(10):
            try:
                os.replace(tmp_path, self.path)
                break
            except PermissionError:
                if attempt >= 9:
                    raise
                time.sleep(0.005)

    def save_scout(self, scout_name: str, params: dict[str, Any], *, actor: str = "api") -> None:
        overrides = self.load_all()
        overrides[scout_name] = dict(params)
        self.save_all(overrides, actor=actor)

    def delete_scout(self, scout_name: str, *, actor: str = "api") -> bool:
        overrides = self.load_all()
        existed = scout_name in overrides
        if existed:
            overrides.pop(scout_name, None)
            self.save_all(overrides, actor=actor)
        return existed

    def validate_params(
        self,
        scout_name: str,
        params: dict[str, Any],
        definitions: list[ScoutDefinition],
    ) -> list[str]:
        warnings: list[str] = []
        definition = next((item for item in definitions if item.name == scout_name), None)
        if definition is None:
            return [f"Unknown scout: {scout_name}"]

        param_types = {parameter.name: parameter.type for parameter in definition.parameters}
        for name, value in params.items():
            if name not in param_types:
                warnings.append(f"Unknown parameter '{name}' for scout '{scout_name}'")
                continue
            expected = str(param_types[name] or "").lower()
            if not self._is_expected_type(value, expected):
                actual = type(value).__name__
                warnings.append(
                    f"Parameter '{name}' expected type '{expected}' but received '{actual}'"
                )
        return warnings

    @staticmethod
    def _is_expected_type(value: Any, expected: str) -> bool:
        if expected == "string":
            return isinstance(value, str)
        if expected == "list":
            return isinstance(value, list)
        if expected == "boolean":
            return isinstance(value, bool)
        if expected == "integer":
            return isinstance(value, int) and not isinstance(value, bool)
        if expected == "number":
            return (isinstance(value, int) or isinstance(value, float)) and not isinstance(value, bool)
        return True


__all__ = ["ScoutConfigStore"]
