# Copyright (c) 2025 CloudZero - ALL RIGHTS RESERVED - PROPRIETARY AND CONFIDENTIAL
# Unauthorized copying of this file and/or project, via any medium is strictly prohibited.
# Direct all questions to legal@cloudzero.com

"""System metadata manager with centralized caching and graceful error handling using v4 API."""

import json
from datetime import UTC, datetime, timedelta
from pathlib import Path

# Get version from src package to ensure consistent version reporting
from src import __version__ as cli_version
from src.utils.console import eprint


class SystemMetadataManager:
    """Singleton manager for system metadata with caching and graceful error handling using v4 API."""

    _instance: "SystemMetadataManager | None" = None
    _metadata: object | None = None  # Will hold SystemMetadata from v4 API
    _error_shown: bool = False
    _fetch_attempted: bool = False  # Prevents repeated failing API calls in the same session
    _cache_file: Path

    def __new__(cls) -> "SystemMetadataManager":
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            # Initialize cache file path
            cls._instance._cache_file = Path.home() / ".cloudzero" / "system_metadata_cache.json"
        return cls._instance

    def clear_cache(self) -> None:
        """Clear both in-memory and persistent cache.

        This forces a fresh fetch from the backend on the next metadata request.
        Useful for ensuring accurate version information.
        """
        self._metadata = None
        self._fetch_attempted = False
        try:
            if self._cache_file.exists():
                self._cache_file.unlink()
        except Exception:
            pass  # Silent failure - don't break CLI if cache clear fails

    def _has_valid_token(self) -> bool:
        """Check if user has a valid auth token (used to gate caching).

        Returns:
            bool: True if a valid token exists, False otherwise
        """
        try:
            from src.auth.simple_auth import get_authenticator

            authenticator = get_authenticator()
            token = authenticator.get_stored_token()
            return bool(token and "expires_at" in token)
        except Exception:
            return False

    def _load_cached_metadata(self) -> object | None:
        """Load cached metadata from file if valid.

        Returns:
            SystemMetadata | None: Cached metadata if valid, None otherwise
        """
        try:
            if not self._cache_file.exists():
                return None

            # Read cache file
            cache_data = json.loads(self._cache_file.read_text())

            # Check if cache is still valid based on token expiry
            if "expires_at" not in cache_data:
                return None

            cache_expiry = datetime.fromisoformat(cache_data["expires_at"])
            if datetime.now(UTC) >= cache_expiry:
                # Cache expired, remove file
                self._cache_file.unlink(missing_ok=True)
                return None

            # Load metadata from cache
            if "metadata" in cache_data:
                from src.api.v4_system import SystemMetadata

                metadata = SystemMetadata(**cache_data["metadata"])

                # Reject cached data that indicates an error/offline state
                if metadata.cz_ops_services is None:
                    self._cache_file.unlink(missing_ok=True)
                    return None

                return metadata

        except Exception:
            # If cache loading fails, remove corrupted cache
            self._cache_file.unlink(missing_ok=True)

        return None

    def _save_metadata_to_cache(self, metadata: object) -> None:
        """Save metadata to persistent cache with 15-minute TTL.

        Only saves metadata that indicates a successful API response (cz_ops_services is set).

        Args:
            metadata: SystemMetadata object to cache
        """
        try:
            if not self._has_valid_token():
                return

            # Never persist error/offline metadata — it would poison future sessions
            if metadata.cz_ops_services is None:
                return

            # Ensure cache directory exists
            self._cache_file.parent.mkdir(parents=True, exist_ok=True)

            # Set cache expiry to 15 minutes (not token expiry which could be hours/days)
            # This ensures version information stays fresh
            cache_expiry = datetime.now(UTC) + timedelta(minutes=15)

            # Prepare cache data
            cache_data = {
                "cached_at": datetime.now(UTC).isoformat(),
                "expires_at": cache_expiry.isoformat(),
                "metadata": metadata.model_dump(),
            }

            # Write cache file with secure permissions
            self._cache_file.write_text(json.dumps(cache_data, indent=2))
            self._cache_file.chmod(0o600)  # Owner read/write only

        except Exception:
            # Silent failure - don't break CLI if caching fails
            pass

    def _make_offline_metadata(self):
        """Create offline metadata placeholder (not cached in singleton)."""
        from src.api.v4_system import SystemMetadata

        return SystemMetadata(
            cz_ops_cli=cli_version,
            cz_ops_services=None,
        )

    def get_metadata(self, arguments: dict | None = None):
        """Get system metadata with caching and graceful error handling using v4 API.

        Uses persistent file-based caching with 15-minute TTL for optimal performance.
        Only successful API responses are cached in the singleton — error/offline state is
        returned without caching so subsequent calls can retry.

        Args:
            arguments: Command-line arguments

        Returns:
            SystemMetadata: Either valid metadata from v4 API or offline metadata with error details
        """
        if self._metadata is not None:
            return self._metadata

        if arguments is None:
            arguments = {}

        # Try to load from persistent cache first
        cached_metadata = self._load_cached_metadata()
        if cached_metadata is not None:
            if arguments.get("--debug"):
                eprint("DEBUG: Using cached system metadata (valid until cache expires)")
            self._metadata = cached_metadata
            return self._metadata

        # If we already tried the API this session and it failed, don't retry
        if self._fetch_attempted:
            return self._make_offline_metadata()

        # Try to get system metadata from the v4 API
        self._fetch_attempted = True
        try:
            from src.api.v4_system import get_system_metadata

            debug = arguments.get("--debug", False)
            metadata = get_system_metadata(debug=debug)

            # Only cache successful responses in the singleton
            if metadata.cz_ops_services is not None:
                self._metadata = metadata
                self._save_metadata_to_cache(metadata)
                return self._metadata

            # API returned but without service version — treat as offline
            return metadata

        except Exception as e:
            if not self._error_shown:
                if not arguments.get("--debug"):
                    eprint("⚠️  Unable to verify CLI version - continuing anyway")
                else:
                    eprint(f"⚠️  Unable to verify CLI version: {e}")
                self._error_shown = True

            return self._make_offline_metadata()

    def is_available(self) -> bool:
        """Check if system metadata API is available.

        Returns:
            bool: True if API is working, False if offline
        """
        metadata = self.get_metadata()
        return metadata.cz_ops_services is not None

    def reset_cache(self):
        """Reset both in-memory and persistent cached metadata (useful for testing)."""
        self._metadata = None
        self._error_shown = False
        self._fetch_attempted = False
        # Also remove persistent cache file
        self._cache_file.unlink(missing_ok=True)


# Global instance
_system_metadata_manager = SystemMetadataManager()


def get_system_metadata_manager() -> SystemMetadataManager:
    """Get the global system metadata manager instance.

    Returns:
        SystemMetadataManager: The singleton instance
    """
    return _system_metadata_manager
