"""
Utility Functions
File operations, JSON handling, async helpers
"""

import json
import os
import asyncio
import logging
from typing import Any, Dict, Optional, TypeVar, Callable
from pathlib import Path
from functools import wraps
from contextlib import contextmanager
try:
    import fcntl
except ImportError:
    fcntl = None  # Windows doesn't have fcntl
import tempfile
import shutil

logger = logging.getLogger(__name__)

T = TypeVar('T')


class AtomicFileWriter:
    """Écriture atomique de fichiers pour éviter corruption"""

    def __init__(self, filepath: str):
        self.filepath = Path(filepath)
        self.temp_file = None

    def __enter__(self):
        """Créer fichier temporaire"""
        self.temp_file = tempfile.NamedTemporaryFile(
            mode='w',
            encoding='utf-8',
            delete=False,
            dir=self.filepath.parent,
            prefix=f'.tmp_{self.filepath.name}_'
        )
        return self.temp_file

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Déplacer le fichier temporaire ou nettoyer"""
        if self.temp_file:
            self.temp_file.close()

            if exc_type is None:
                # Pas d'erreur, déplacer le fichier
                try:
                    shutil.move(self.temp_file.name, self.filepath)
                except Exception as e:
                    logger.error(f"Erreur déplacement fichier: {e}")
                    os.unlink(self.temp_file.name)
                    raise
            else:
                # Erreur, supprimer le fichier temporaire
                try:
                    os.unlink(self.temp_file.name)
                except:
                    pass


@contextmanager
def file_lock(filepath: str, timeout: float = 5.0):
    """Context manager pour lock fichier (éviter race conditions)"""
    lock_file = f"{filepath}.lock"
    lock_fd = None

    try:
        lock_fd = os.open(lock_file, os.O_CREAT | os.O_WRONLY)

        # Tenter d'acquérir le lock
        if os.name != 'nt':  # Unix
            import time
            start = time.time()
            while time.time() - start < timeout:
                try:
                    fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
                    break
                except IOError:
                    time.sleep(0.1)
            else:
                raise TimeoutError(f"Cannot acquire lock for {filepath}")

        yield

    finally:
        if lock_fd is not None:
            try:
                if os.name != 'nt':
                    fcntl.flock(lock_fd, fcntl.LOCK_UN)
                os.close(lock_fd)
            except:
                pass

            try:
                os.unlink(lock_file)
            except:
                pass


def load_json_file(filepath: str, default: Any = None) -> Any:
    """
    Charger un fichier JSON de manière sécurisée

    Args:
        filepath: Chemin du fichier
        default: Valeur par défaut si erreur ou fichier inexistant

    Returns:
        Données JSON ou valeur par défaut
    """
    try:
        if not os.path.exists(filepath):
            logger.debug(f"File not found: {filepath}")
            return default if default is not None else {}

        with file_lock(filepath, timeout=2.0):
            with open(filepath, 'r', encoding='utf-8') as f:
                data = json.load(f)
                return data

    except json.JSONDecodeError as e:
        logger.error(f"JSON decode error in {filepath}: {e}")
        return default if default is not None else {}
    except TimeoutError:
        logger.warning(f"Timeout acquiring lock for {filepath}")
        # Try reading anyway without lock
        try:
            with open(filepath, 'r', encoding='utf-8') as f:
                return json.load(f)
        except:
            return default if default is not None else {}
    except Exception as e:
        logger.error(f"Error reading {filepath}: {e}")
        return default if default is not None else {}


def save_json_file(filepath: str, data: Any, pretty: bool = True) -> bool:
    """
    Sauvegarder un fichier JSON de manière atomique

    Args:
        filepath: Chemin du fichier
        data: Données à sauvegarder
        pretty: Formater avec indentation

    Returns:
        True si succès, False sinon
    """
    try:
        # Créer le dossier parent si nécessaire
        os.makedirs(os.path.dirname(filepath) or '.', exist_ok=True)

        with file_lock(filepath, timeout=2.0):
            with AtomicFileWriter(filepath) as f:
                if pretty:
                    json.dump(data, f, indent=2, ensure_ascii=False)
                else:
                    json.dump(data, f, ensure_ascii=False)
                f.write('\n')  # Ajouter newline final

        return True

    except Exception as e:
        logger.error(f"Error writing {filepath}: {e}")
        return False


def async_to_sync(coro_func: Callable) -> Callable:
    """Décorateur pour exécuter une fonction async de manière synchrone"""
    @wraps(coro_func)
    def wrapper(*args, **kwargs):
        try:
            loop = asyncio.get_event_loop()
        except RuntimeError:
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)

        return loop.run_until_complete(coro_func(*args, **kwargs))

    return wrapper


def safe_float(value: Any, default: float = 0.0) -> float:
    """Convertir en float de manière sécurisée"""
    try:
        return float(value)
    except (ValueError, TypeError):
        return default


def safe_int(value: Any, default: int = 0) -> int:
    """Convertir en int de manière sécurisée"""
    try:
        return int(value)
    except (ValueError, TypeError):
        return default


def truncate_string(s: str, max_length: int = 100, suffix: str = "...") -> str:
    """Tronquer une chaîne si trop longue"""
    if len(s) <= max_length:
        return s
    return s[:max_length - len(suffix)] + suffix


class CircularBuffer:
    """Buffer circulaire pour logging/monitoring"""

    def __init__(self, max_size: int = 100):
        self.max_size = max_size
        self.buffer = []
        self.index = 0

    def append(self, item: Any):
        """Ajouter un élément au buffer"""
        if len(self.buffer) < self.max_size:
            self.buffer.append(item)
        else:
            self.buffer[self.index] = item
            self.index = (self.index + 1) % self.max_size

    def get_all(self) -> list:
        """Récupérer tous les éléments dans l'ordre"""
        if len(self.buffer) < self.max_size:
            return self.buffer.copy()
        return self.buffer[self.index:] + self.buffer[:self.index]

    def clear(self):
        """Vider le buffer"""
        self.buffer.clear()
        self.index = 0


def format_pnl(pnl: float, currency: str = "€") -> str:
    """Formater un P&L avec couleur"""
    sign = "+" if pnl >= 0 else ""
    return f"{sign}{pnl:.2f} {currency}"


def format_percentage(value: float) -> str:
    """Formater un pourcentage"""
    sign = "+" if value >= 0 else ""
    return f"{sign}{value:.2f}%"
