from __future__ import annotations

import json
import threading
import time
from pathlib import Path
from typing import Any

import config_manager
from strategy_registry.registry import get_strategy_registry
from strategy_sdk.types import StrategyHealth, StrategyInitContext


def _effective_params_file() -> Path:
    return config_manager.get_logs_dir() / "effective_params.json"


def _runtime_status_file() -> Path:
    return config_manager.get_logs_dir() / "strategy_runtime_status.json"


def _load_effective_payload() -> dict[str, Any]:
    path = _effective_params_file()
    if not path.exists():
        return {}
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return {}


def _config_revision_from_payload(payload: dict[str, Any]) -> str:
    for key in ("config_revision", "revision", "synced_at", "generated_at"):
        value = payload.get(key)
        if value:
            return str(value)
    path = _effective_params_file()
    if path.exists():
        try:
            return f"mtime-{int(path.stat().st_mtime)}"
        except OSError:
            return "manual"
    return "manual"


def _runtime_scheduler_from_payload(payload: dict[str, Any]) -> dict[str, Any]:
    scheduler = payload.get("runtime_scheduler", {})
    return scheduler if isinstance(scheduler, dict) else {}


def _active_runtime_view(payload: dict[str, Any], scheduler: dict[str, Any], config_revision: str) -> dict[str, Any]:
    active_strategy_id = str(payload.get("strategy_type", "ema_macd") or "ema_macd")
    active_strategy_ids = [
        str(item)
        for item in scheduler.get("active_strategy_ids", [])
        if str(item).strip()
    ] if isinstance(scheduler.get("active_strategy_ids", []), list) else []
    active_instance_source = scheduler.get("active_instance_ids", scheduler.get("active_instances", []))
    active_instance_ids = [
        str(item)
        for item in active_instance_source
        if str(item).strip()
    ] if isinstance(active_instance_source, list) else []
    if not active_strategy_ids:
        active_strategy_ids = [active_strategy_id]
    return {
        "strategy_id": active_strategy_id,
        "strategy_ids": active_strategy_ids,
        "instance_ids": active_instance_ids,
        "mode": str(scheduler.get("mode", "single_active") or "single_active"),
        "arbiter": str(scheduler.get("arbiter", "allocation-weighted") or "allocation-weighted"),
        "reason": str(scheduler.get("reason", "runtime_scheduler") or "runtime_scheduler"),
        "selected_strategy_id": str(scheduler.get("selected_strategy_id", active_strategy_id) or active_strategy_id),
        "selected_instance_id": str(scheduler.get("selected_instance_id", "") or ""),
        "config_revision": config_revision,
        "effective_params_path": str(_effective_params_file()),
    }


class StrategyRuntime:
    def __init__(self):
        self._registry = get_strategy_registry()
        self._instances: dict[str, Any] = {}
        self._health: dict[str, StrategyHealth] = {}
        self._lock = threading.RLock()

    def _write_status_snapshot(self) -> None:
        payload = _load_effective_payload()
        config_revision = _config_revision_from_payload(payload)
        scheduler = _runtime_scheduler_from_payload(payload)
        status = {
            "ts_ms": int(time.time() * 1000),
            "active": _active_runtime_view(payload, scheduler, config_revision),
            "runtime_scheduler": scheduler,
            "registered": [manifest.strategy_id for manifest in self._registry.list_manifests()],
            "manifests": [vars(manifest) for manifest in self._registry.list_manifests()],
            "loaded": list(self._instances.keys()),
            "health": {key: vars(value) for key, value in self._health.items()},
        }
        try:
            path = _runtime_status_file()
            path.parent.mkdir(parents=True, exist_ok=True)
            path.write_text(json.dumps(status, ensure_ascii=True, indent=2), encoding="utf-8")
        except Exception:
            pass

    def _build_context(self, strategy_id: str) -> StrategyInitContext:
        payload = _load_effective_payload()
        return StrategyInitContext(
            strategy_id=strategy_id,
            strategy_version=self._health.get(strategy_id, StrategyHealth(strategy_id=strategy_id)).strategy_version,
            config_revision=_config_revision_from_payload(payload),
            symbol=str(payload.get("symbol", "XAUUSD") or "XAUUSD"),
            timeframe=str(payload.get("timeframe", "M5") or "M5"),
            mode="live",
            config=payload.get("params", {}) if isinstance(payload.get("params", {}), dict) else {},
            shared_features={},
            env={"source": "effective_params"},
        )

    def get_legacy_compatible_generator(
        self,
        strategy_id: str | None,
        params: dict[str, Any] | None = None,
        force_reload: bool = False,
    ):
        resolved = self._registry.resolve(strategy_id)
        with self._lock:
            if force_reload or resolved not in self._instances:
                adapter = self._registry.instantiate_legacy(resolved, params=params)
                ctx = self._build_context(resolved)
                adapter.initialize(ctx)
                self._instances[resolved] = adapter
                self._health[resolved] = StrategyHealth(
                    strategy_id=resolved,
                    loaded=True,
                    healthy=True,
                    last_error=None,
                    last_run_at_ms=None,
                    last_latency_ms=None,
                    consecutive_failures=0,
                    config_revision=ctx.config_revision,
                    strategy_version=adapter.manifest().version,
                )
            self._write_status_snapshot()
            return self._instances[resolved]

    def mark_execution(
        self,
        strategy_id: str,
        started_at_ms: int,
        error: Exception | None = None,
    ) -> None:
        resolved = self._registry.resolve(strategy_id)
        with self._lock:
            health = self._health.get(resolved)
            if health is None:
                return
            health.last_run_at_ms = int(time.time() * 1000)
            health.last_latency_ms = max(0.0, health.last_run_at_ms - started_at_ms)
            if error is None:
                health.healthy = True
                health.last_error = None
                health.consecutive_failures = 0
            else:
                health.healthy = False
                health.last_error = str(error)
                health.consecutive_failures += 1
            self._write_status_snapshot()

    def get_health(self, strategy_id: str | None = None) -> dict[str, Any]:
        with self._lock:
            if strategy_id is None:
                return {key: vars(value) for key, value in self._health.items()}
            resolved = self._registry.resolve(strategy_id)
            value = self._health.get(resolved)
            return vars(value) if value is not None else {}

    def get_status(self, strategy_id: str | None = None) -> dict[str, Any]:
        if strategy_id is None:
            status = {
                "registered": [m.strategy_id for m in self._registry.list_manifests()],
                "manifests": [vars(m) for m in self._registry.list_manifests()],
                "loaded": list(self._instances.keys()),
                "health": self.get_health(),
            }
            self._write_status_snapshot()
            return status
        resolved = self._registry.resolve(strategy_id)
        adapter = self._instances.get(resolved)
        status = {
            "strategy_id": resolved,
            "loaded": adapter is not None,
            "manifest": (vars(adapter.manifest()) if adapter else None),
            "health": self.get_health(resolved),
        }
        self._write_status_snapshot()
        return status

    def reset(self, strategy_id: str | None = None):
        with self._lock:
            if strategy_id is None:
                old = list(self._instances.keys())
                self._instances.clear()
                self._health.clear()
                self._write_status_snapshot()
                return old
            resolved = self._registry.resolve(strategy_id)
            old = self._instances.pop(resolved, None)
            self._health.pop(resolved, None)
            self._write_status_snapshot()
            return old


_DEFAULT_RUNTIME: StrategyRuntime | None = None


def get_strategy_runtime() -> StrategyRuntime:
    global _DEFAULT_RUNTIME
    if _DEFAULT_RUNTIME is None:
        _DEFAULT_RUNTIME = StrategyRuntime()
    return _DEFAULT_RUNTIME
