# Copyright (c) 2020 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
#

import os
import sys
from pathlib import Path

import httpx
from packaging import version
from pydantic import BaseModel

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


class VersionInfo(BaseModel):
    version: str
    latest: bool
    ahead: bool = False
    latest_release: str | None = None  # Latest release version when ahead


def cli_version_info(arguments=None) -> VersionInfo:
    """Get CLI version information using cached SystemMetadataManager.

    Args:
        arguments: Command line arguments dict

    Returns:
        VersionInfo: Version comparison data
    """
    try:
        # Use SystemMetadataManager for cached access to v4 system API
        from src.utils.system_metadata import get_system_metadata_manager

        # Get system metadata using the cached manager
        manager = get_system_metadata_manager()
        metadata = manager.get_metadata(arguments)

        # Extract CLI version from v4 response (SystemMetadata object)
        latest_cli_version = metadata.cz_ops_cli

        # If no version info available, return current version as latest
        if not latest_cli_version:
            return VersionInfo(version=cli_version, latest=True)

        # Ignore dev versions when comparing - they should not trigger "new version available"
        # Dev versions have -dev suffix or .devN suffix (e.g., "3.0.1-dev" or "3.0.1.dev0")
        if "-dev" in latest_cli_version or ".dev" in latest_cli_version:
            # Backend returned a dev version - ignore it and consider current as latest
            return VersionInfo(version=cli_version, latest=True)

        current_version = version.parse(cli_version)
        latest_version = version.parse(latest_cli_version)

        if current_version == latest_version:
            return VersionInfo(version=latest_cli_version, latest=True)
        elif current_version > latest_version:
            return VersionInfo(version=cli_version, latest=True, ahead=True, latest_release=latest_cli_version)
        else:
            return VersionInfo(version=latest_cli_version, latest=False)

    except Exception as e:
        # Handle errors gracefully - if API fails, assume current version is latest
        # Note: SystemMetadataManager already handles error display, so we don't duplicate it here
        if arguments and arguments.get("--debug"):
            eprint(f"⚠️  Additional error in cli_version_info: {e}")

        return VersionInfo(version=cli_version, latest=True)


def touch(fname, times=None):
    with open(fname, "a+"):
        os.utime(fname, times)


def download_file(url, local_filename, show_status=False):
    if show_status:
        print(f"Downloading {local_filename}", end="", flush=True)

    with httpx.stream("GET", url) as r:
        r.raise_for_status()
        with open(local_filename, "wb") as f:
            for chunk in r.iter_bytes(chunk_size=8192):
                f.write(chunk)
                if show_status:
                    print(".", end="", flush=True)

    if show_status:
        print(".done!")

    return local_filename


def validate_input_file(
    file_path: str,
    file_type: str = "file",
    required_extension: str | None = None,
    suggest_similar: bool = True,
    max_suggestions: int = 5,
) -> tuple[bool, str]:
    """
    Validates if a file exists and meets the specified requirements.

    Args:
        file_path: Path to the file to validate
        file_type: Description of the file type for error messages (e.g., 'CSV file', 'configuration file')
        required_extension: If specified, validates the file has this extension
        suggest_similar: Whether to suggest similar files in case of error
        max_suggestions: Maximum number of similar files to suggest

    Returns:
        Tuple of (is_valid, error_message)
        - is_valid: True if file is valid, False otherwise
        - error_message: Empty string if valid, otherwise error description
    """
    if not file_path:
        return False, f"No {file_type} path provided"

    # Expand user directory if present (~/something)
    expanded_path = os.path.expanduser(file_path)

    # Check if file exists
    if not os.path.exists(expanded_path):
        error_message = f"The {file_type} '{expanded_path}' could not be found"

        # Try to find similar files if requested
        if suggest_similar:
            try:
                directory = os.path.dirname(expanded_path) or "."
                if os.path.exists(directory):
                    # If extension was specified, filter by it
                    extension_filter = f"*{required_extension}" if required_extension else "*"

                    # Get all files with matching extension in the directory
                    if required_extension:
                        similar_files = list(Path(directory).glob(extension_filter))
                    else:
                        similar_files = [f for f in Path(directory).iterdir() if f.is_file()]

                    if similar_files:
                        # Sort files by modification time (newest first) to show most relevant ones
                        similar_files.sort(key=lambda x: os.path.getmtime(x), reverse=True)

                        # Add file suggestions to error message
                        suggestions = [f"Available {file_type}s in this directory:"]
                        for _i, file in enumerate(similar_files[:max_suggestions]):
                            suggestions.append(f"   - {file}")
                        if len(similar_files) > max_suggestions:
                            suggestions.append(f"   - ... and {len(similar_files) - max_suggestions} more")
                        error_message += "\n\n" + "\n".join(suggestions)
            except Exception:
                pass  # Skip suggestions on any error

        return False, error_message

    # Check if it's actually a file (not a directory)
    if not os.path.isfile(expanded_path):
        return False, f"'{expanded_path}' is not a valid file"

    # Check file extension if required
    if required_extension:
        if not expanded_path.lower().endswith(required_extension.lower()):
            return False, f"The file '{expanded_path}' does not have the required {required_extension} extension"

    # Check if file is readable
    try:
        with open(expanded_path) as _:
            pass
    except PermissionError:
        return False, f"Permission denied when trying to access '{expanded_path}'"
    except Exception as e:
        return False, f"Error accessing file '{expanded_path}': {str(e)}"

    # All checks passed
    return True, ""


def check_input_file(file_path: str, file_type: str = "file", required_extension: str | None = None, exit_on_error: bool = True) -> str:
    """
    Checks if a file exists and meets requirements, with user-friendly error messages.

    Args:
        file_path: Path to the file to check
        file_type: Description of the file type for messages (e.g., 'CSV file')
        required_extension: If specified, validates the file has this extension
        exit_on_error: Whether to exit the program if the file is invalid

    Returns:
        The expanded file path if valid

    Raises:
        SystemExit: If the file is invalid and exit_on_error is True
    """
    is_valid, error_message = validate_input_file(file_path, file_type=file_type, required_extension=required_extension)

    if not is_valid:
        eprint(f"❌ {error_message}")
        eprint("💡 Tip: Check that the file exists and the path is correct")
        eprint(f"🔍 Current working directory: {os.getcwd()}")

        if exit_on_error:
            sys.exit(1)
        else:
            return ""

    return os.path.expanduser(file_path)
