"""
License Manager for Plumber Local Agent

Handles license key storage, machine fingerprinting, and offline validation cache.
Works with the backend LicenseManager for full validation lifecycle.

Storage: ~/.plumber-agent/license.json
Env override: PLUMBER_LICENSE_KEY
"""

import os
import json
import hashlib
import logging
import platform
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional, Dict

logger = logging.getLogger(__name__)

# Offline grace period (days)
OFFLINE_GRACE_DAYS = 7


class AgentLicenseManager:
    """Manages license key storage and machine fingerprinting for the local agent."""

    def __init__(self, config_dir: Optional[Path] = None):
        self.config_dir = config_dir or (Path.home() / ".plumber-agent")
        self.license_file = self.config_dir / "license.json"
        self._license_data: Optional[Dict] = None
        self._fingerprint: Optional[str] = None

    # ──────────────────────────────────────────────────
    # License Key Loading / Saving
    # ──────────────────────────────────────────────────

    def load_license(self) -> Optional[str]:
        """
        Load license key from environment variable or config file.
        Environment variable takes precedence.

        Returns:
            License key string or None if not found.
        """
        # 1. Check environment variable first
        env_key = os.getenv("PLUMBER_LICENSE_KEY")
        if env_key and env_key.strip():
            logger.info("[LICENSE] Loaded license key from PLUMBER_LICENSE_KEY env var")
            return env_key.strip()

        # 2. Check config file
        data = self._load_license_data()
        if data and data.get("license_key"):
            logger.info("[LICENSE] Loaded license key from config file")
            return data["license_key"]

        return None

    def save_license(self, license_key: str):
        """
        Save license key to config file.

        Args:
            license_key: The license key to save.
        """
        self.config_dir.mkdir(parents=True, exist_ok=True)

        data = self._load_license_data() or {}
        data["license_key"] = license_key
        data["saved_at"] = datetime.now(timezone.utc).isoformat()

        with open(self.license_file, "w") as f:
            json.dump(data, f, indent=2)

        self._license_data = data
        logger.info(f"[LICENSE] Saved license key to {self.license_file}")

    def clear_license(self):
        """Remove saved license key and cache."""
        if self.license_file.exists():
            data = self._load_license_data() or {}
            data.pop("license_key", None)
            data.pop("cached_validation", None)
            data.pop("saved_at", None)

            with open(self.license_file, "w") as f:
                json.dump(data, f, indent=2)

            self._license_data = data
            logger.info("[LICENSE] Cleared license key and cache")

    # ──────────────────────────────────────────────────
    # Machine Fingerprint
    # ──────────────────────────────────────────────────

    def generate_fingerprint(self) -> str:
        """
        Generate a stable machine fingerprint from hardware identifiers.
        Uses SHA256(CPU_ID + MAC_ADDRESS)[:32].

        Returns:
            32-character hex fingerprint string.
        """
        if self._fingerprint:
            return self._fingerprint

        cpu_id = platform.processor() or platform.machine() or "unknown_cpu"
        mac_addr = str(uuid.getnode())
        raw = f"{cpu_id}:{mac_addr}"

        self._fingerprint = hashlib.sha256(raw.encode()).hexdigest()[:32]
        logger.debug(f"[LICENSE] Generated machine fingerprint: {self._fingerprint[:8]}...")
        return self._fingerprint

    def get_machine_name(self) -> str:
        """Get a human-readable machine name."""
        return platform.node() or "unknown"

    # ──────────────────────────────────────────────────
    # Offline Validation Cache
    # ──────────────────────────────────────────────────

    def is_cache_valid(self) -> bool:
        """
        Check if the local validation cache is still within the grace period.

        Returns:
            True if cache exists and is less than OFFLINE_GRACE_DAYS old.
        """
        data = self._load_license_data()
        if not data or not data.get("cached_validation"):
            return False

        cache = data["cached_validation"]
        last_validated = cache.get("last_validated")
        if not last_validated:
            return False

        try:
            validated_dt = datetime.fromisoformat(last_validated)
            if validated_dt.tzinfo is None:
                validated_dt = validated_dt.replace(tzinfo=timezone.utc)

            now = datetime.now(timezone.utc)
            age_days = (now - validated_dt).total_seconds() / 86400

            if age_days <= OFFLINE_GRACE_DAYS:
                logger.info(f"[LICENSE] Offline cache valid ({age_days:.1f} days old, grace={OFFLINE_GRACE_DAYS} days)")
                return True
            else:
                logger.warning(f"[LICENSE] Offline cache expired ({age_days:.1f} days old, grace={OFFLINE_GRACE_DAYS} days)")
                return False
        except (ValueError, TypeError) as e:
            logger.warning(f"[LICENSE] Cache timestamp parse error: {e}")
            return False

    def get_cached_validation(self) -> Optional[Dict]:
        """
        Get cached validation result for offline use.

        Returns:
            Cached validation dict or None.
        """
        data = self._load_license_data()
        if not data:
            return None
        return data.get("cached_validation")

    def update_cache(self, validation_result: Dict):
        """
        Store a successful validation result for offline use.

        Args:
            validation_result: Dict with tier, user_id, etc. from backend validation.
        """
        self.config_dir.mkdir(parents=True, exist_ok=True)

        data = self._load_license_data() or {}
        data["cached_validation"] = {
            "last_validated": datetime.now(timezone.utc).isoformat(),
            "tier": validation_result.get("tier", "unknown"),
            "user_id": validation_result.get("user_id"),
            "license_status": "active",
        }

        with open(self.license_file, "w") as f:
            json.dump(data, f, indent=2)

        self._license_data = data
        logger.info(f"[LICENSE] Updated offline cache (tier={validation_result.get('tier')})")

    def clear_cache(self):
        """Clear the offline validation cache."""
        data = self._load_license_data() or {}
        data.pop("cached_validation", None)

        if self.license_file.exists():
            with open(self.license_file, "w") as f:
                json.dump(data, f, indent=2)

        self._license_data = data
        logger.info("[LICENSE] Cleared offline cache")

    # ──────────────────────────────────────────────────
    # Status / Info
    # ──────────────────────────────────────────────────

    def get_license_info(self) -> Dict:
        """
        Get current license status info for display.

        Returns:
            Dict with license_key (masked), machine_fingerprint, cache status, etc.
        """
        key = self.load_license()
        cache = self.get_cached_validation()
        cache_valid = self.is_cache_valid()

        info = {
            "has_license": key is not None,
            "license_key_masked": self._mask_key(key) if key else None,
            "machine_fingerprint": self.generate_fingerprint(),
            "machine_name": self.get_machine_name(),
            "offline_cache_valid": cache_valid,
        }

        if cache:
            info["cached_tier"] = cache.get("tier")
            info["last_validated"] = cache.get("last_validated")

        return info

    # ──────────────────────────────────────────────────
    # Private Helpers
    # ──────────────────────────────────────────────────

    def _load_license_data(self) -> Optional[Dict]:
        """Load the full license JSON file."""
        if self._license_data is not None:
            return self._license_data

        if not self.license_file.exists():
            return None

        try:
            with open(self.license_file, "r") as f:
                self._license_data = json.load(f)
            return self._license_data
        except (json.JSONDecodeError, OSError) as e:
            logger.warning(f"[LICENSE] Error reading license file: {e}")
            return None

    @staticmethod
    def _mask_key(key: str) -> str:
        """Mask a license key for display (show first 10 and last 4 chars)."""
        if not key or len(key) < 16:
            return key or ""
        return key[:10] + "..." + key[-4:]
