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

"""
File system utilities for common file operations.

This module provides functions for:
- File validation and checking
- File downloading
- File timestamps
- Working with CSV files
"""

import csv
import os
import os.path
from decimal import Decimal, InvalidOperation
from typing import Any

import httpx
from rich.progress import BarColumn, DownloadColumn, Progress, TextColumn, TimeRemainingColumn, TransferSpeedColumn


def touch(path: str) -> None:
    """
    Touch a file to update its access/modification time or create it if it doesn't exist.

    Args:
        path: Path to the file to touch
    """
    with open(path, "a"):
        os.utime(path, None)


def download_file(url: str, output_filename: str, show_progress: bool = False) -> bool:
    """
    Download a file from a URL to a local path with optional progress display.

    Args:
        url: URL to download from
        output_filename: Path where to save the file
        show_progress: Whether to display a progress bar during download

    Returns:
        True if download was successful, False otherwise
    """
    try:
        with httpx.stream("GET", url) as response:
            response.raise_for_status()
            total_size = int(response.headers.get("content-length", 0))

            # Create directory if needed
            os.makedirs(os.path.dirname(os.path.abspath(output_filename)), exist_ok=True)

            # If requested, show a progress bar
            if show_progress:
                with Progress(
                    TextColumn("[bold blue]{task.description}", justify="right"),
                    BarColumn(bar_width=40),
                    "[progress.percentage]{task.percentage:>3.1f}%",
                    "•",
                    DownloadColumn(),
                    "•",
                    TransferSpeedColumn(),
                    "•",
                    TimeRemainingColumn(),
                ) as progress:
                    download_task = progress.add_task(f"Downloading {output_filename}", total=total_size)

                    with open(output_filename, "wb") as f:
                        for chunk in response.iter_bytes(chunk_size=8192):
                            if chunk:
                                f.write(chunk)
                                progress.update(download_task, advance=len(chunk))
            else:
                with open(output_filename, "wb") as f:
                    for chunk in response.iter_bytes(chunk_size=8192):
                        if chunk:
                            f.write(chunk)

            print(f"Downloaded to {output_filename}")
            return True

    except httpx.RequestError as e:
        print(f"Error downloading file: {e}")
        return False


def validate_input_file(file_path: str, file_type: str = "file", required_extension: str = None) -> bool:
    """
    Validate that a file exists and has the correct extension if specified.

    Args:
        file_path: Path to the file to validate
        file_type: Description of the file type (for error messages)
        required_extension: Required file extension including the dot

    Returns:
        True if the file is valid, False otherwise
    """
    if not os.path.exists(file_path):
        print(f"Error: {file_type.capitalize()} '{file_path}' does not exist.")
        return False

    if not os.path.isfile(file_path):
        print(f"Error: '{file_path}' is not a valid {file_type}.")
        return False

    if required_extension and not file_path.lower().endswith(required_extension.lower()):
        print(f"Error: '{file_path}' is not a valid {file_type}. Expected {required_extension} file.")
        return False

    return True


def check_input_file(file_path: str, file_type: str = "file", required_extension: str = None) -> str:
    """
    Check that a file exists and has the correct extension, with better error messages.

    Args:
        file_path: Path to the file to validate
        file_type: Description of the file type (for error messages)
        required_extension: Required file extension including the dot

    Returns:
        Absolute path to the validated file

    Raises:
        FileNotFoundError: If the file doesn't exist
        ValueError: If the file is not of the correct type
    """
    abs_path = os.path.abspath(file_path)

    if not os.path.exists(abs_path):
        raise FileNotFoundError(f"{file_type.capitalize()} '{abs_path}' does not exist.")

    if not os.path.isfile(abs_path):
        raise ValueError(f"'{abs_path}' is not a valid {file_type}.")

    if required_extension and not abs_path.lower().endswith(required_extension.lower()):
        raise ValueError(f"'{abs_path}' is not a valid {file_type}. Expected {required_extension} file.")

    return abs_path


def validate_decimal(value: Any) -> tuple[Decimal, bool, str]:
    """
    Validates and converts a value to a Decimal.

    Args:
        value: The value to validate and convert

    Returns:
        A tuple of (converted_value, success, original_value_string)
    """
    original_value = str(value)

    if value is None:
        return Decimal("0"), True, original_value

    if isinstance(value, str) and not value.strip():
        return Decimal("0"), True, original_value

    try:
        # Try first as a decimal directly
        return Decimal(str(value)), True, original_value
    except (InvalidOperation, ValueError):
        # If it's a string with currency symbols, try to clean it
        if isinstance(value, str):
            # Remove common currency symbols and spaces
            for char in ["$", "€", "£", "¥", ",", " "]:
                value = value.replace(char, "")
            try:
                return Decimal(value), True, original_value
            except (InvalidOperation, ValueError):
                # Return 0 if we can't convert
                return Decimal("0"), False, original_value
        return Decimal("0"), False, original_value


def _read_csv_file(file_path: str, encoding: str = "utf-8") -> list[dict[str, str]]:
    """
    Read a CSV file with multiple encoding fallbacks.

    Args:
        file_path: Path to the CSV file
        encoding: Initial encoding to try

    Returns:
        List of dictionaries, each representing a row from the CSV

    Raises:
        UnicodeDecodeError: If no encoding works
        ValueError: If CSV format is invalid
    """
    encodings = ["utf-8", "latin-1", "iso-8859-1", "windows-1252"]

    # If specified encoding is not in the list, add it first
    if encoding not in encodings:
        encodings.insert(0, encoding)

    last_error = None
    for enc in encodings:
        try:
            with open(file_path, encoding=enc) as csvfile:
                reader = csv.DictReader(csvfile)
                return list(reader)
        except UnicodeDecodeError as e:
            last_error = e
            continue

    # If we got here, none of the encodings worked
    raise last_error or ValueError(f"Failed to read CSV file: {file_path}")
