"""License client — used by the controller to validate and report."""
from __future__ import annotations

import json
import logging
import time
from datetime import datetime, timedelta
from pathlib import Path
from typing import Optional

import httpx

from .models import LicenseInfo, LicenseTier, TelemetryPayload, TIER_DEFAULTS

logger = logging.getLogger("mesh.licensing.client")

_CACHE_PATH = "data/license_cache.json"


class LicenseClient:
    """Client for the mesh license server.

    Validates license on startup, sends periodic heartbeats, and enforces
    feature/node limits.  Falls back to cached validation if the server
    is unreachable (offline grace period).
    """

    def __init__(
        self,
        license_key: str = "",
        server_url: str = "https://license.mesh-optimizer.io",
        cache_path: str = _CACHE_PATH,
        offline_grace_days: int = 7,
    ):
        self.license_key = license_key
        self.server_url = server_url.rstrip("/")
        self.cache_path = Path(cache_path)
        self.offline_grace_days = offline_grace_days
        self._info: Optional[LicenseInfo] = None
        self._last_validated: Optional[datetime] = None
        self._client = httpx.Client(timeout=10.0)

    # ── Public API ───────────────────────────────────────────────────────

    def validate(self) -> LicenseInfo:
        """Validate the license key against the server.

        Returns community-tier info if no key is set or on failure within
        the grace period.
        """
        if not self.license_key:
            self._info = self._community_info()
            logger.info("No license key — running in community mode")
            return self._info

        try:
            resp = self._client.get(f"{self.server_url}/licenses/{self.license_key}")
            if resp.status_code == 200:
                self._info = LicenseInfo.model_validate(resp.json())
                self._last_validated = datetime.utcnow()
                self._save_cache()
                logger.info(
                    "License validated: tier=%s, max_nodes=%d, expires=%s",
                    self._info.tier.value,
                    self._info.max_nodes,
                    self._info.expires_at,
                )
                return self._info
            else:
                logger.warning(
                    "License validation failed: HTTP %d — %s",
                    resp.status_code,
                    resp.text[:200],
                )
        except Exception as exc:
            logger.warning("License server unreachable: %s", exc)

        # Try cached validation
        cached = self._load_cache()
        if cached:
            return cached

        # Fallback to community
        self._info = self._community_info()
        logger.warning("License validation failed — falling back to community mode")
        return self._info

    def heartbeat(
        self,
        node_count: int = 0,
        hardware_summary: Optional[dict] = None,
        version: str = "",
        uptime_hours: float = 0.0,
        avg_cpu_pct: float = 0.0,
        avg_memory_pct: float = 0.0,
        total_jobs_run: int = 0,
        total_retrains: int = 0,
    ) -> bool:
        """Send telemetry heartbeat to the license server."""
        if not self.license_key:
            return True

        payload = TelemetryPayload(
            node_count=node_count,
            hardware_summary=hardware_summary or {},
            mesh_version=version,
            uptime_hours=uptime_hours,
            avg_cpu_pct=avg_cpu_pct,
            avg_memory_pct=avg_memory_pct,
            total_jobs_run=total_jobs_run,
            total_retrains=total_retrains,
        )
        try:
            resp = self._client.post(
                f"{self.server_url}/licenses/{self.license_key}/heartbeat",
                json=payload.model_dump(),
            )
            return resp.status_code == 200
        except Exception as exc:
            logger.debug("Heartbeat failed: %s", exc)
            return False

    def check_feature(self, feature_name: str) -> bool:
        """Check if a feature is enabled for the current license."""
        info = self._info or self._community_info()
        return feature_name in info.features

    def check_node_limit(self, current_count: int) -> bool:
        """Check if the current node count is within the license limit.

        A max_nodes of 0 means unlimited (enterprise).
        """
        info = self._info or self._community_info()
        if info.max_nodes == 0:
            return True
        return current_count <= info.max_nodes

    @property
    def info(self) -> LicenseInfo:
        if self._info is None:
            return self._community_info()
        return self._info

    def close(self):
        self._client.close()

    # ── Internals ────────────────────────────────────────────────────────

    def _community_info(self) -> LicenseInfo:
        defaults = TIER_DEFAULTS[LicenseTier.COMMUNITY]
        return LicenseInfo(
            license_key="",
            tier=LicenseTier.COMMUNITY,
            max_nodes=defaults["max_nodes"],
            features=defaults["features"],
            valid=True,
        )

    def _save_cache(self):
        if not self._info:
            return
        try:
            self.cache_path.parent.mkdir(parents=True, exist_ok=True)
            data = {
                "license": self._info.model_dump(mode="json"),
                "cached_at": datetime.utcnow().isoformat(),
            }
            self.cache_path.write_text(json.dumps(data, indent=2))
        except Exception as exc:
            logger.debug("Failed to save license cache: %s", exc)

    def _load_cache(self) -> Optional[LicenseInfo]:
        try:
            if not self.cache_path.exists():
                return None
            data = json.loads(self.cache_path.read_text())
            cached_at = datetime.fromisoformat(data["cached_at"])
            grace = timedelta(days=self.offline_grace_days)
            if datetime.utcnow() - cached_at > grace:
                logger.warning("License cache expired (older than %d days)", self.offline_grace_days)
                return None
            self._info = LicenseInfo.model_validate(data["license"])
            self._last_validated = cached_at
            logger.info("Using cached license validation (cached %s)", cached_at.isoformat())
            return self._info
        except Exception as exc:
            logger.debug("Failed to load license cache: %s", exc)
            return None
