"""
Core functionality for Gattopy.
"""

import time
import tracemalloc
from contextlib import contextmanager
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional


@dataclass
class ProfileResult:
    """Stores profiling results."""

    function_name: str
    execution_time: Optional[float] = None  # in seconds
    memory_peak: Optional[float] = None  # in MB
    memory_current: Optional[float] = None  # in MB
    calls_count: int = 1
    metadata: Dict[str, Any] = field(default_factory=dict)

    def __str__(self) -> str:
        """Human-readable representation."""
        lines = [f"Profile Results for '{self.function_name}':"]

        if self.execution_time is not None:
            lines.append(f"  Execution Time: {self._format_time(self.execution_time)}")

        if self.memory_peak is not None:
            lines.append(f"  Peak Memory: {self.memory_peak:.2f} MB")

        if self.memory_current is not None:
            lines.append(f"  Current Memory: {self.memory_current:.2f} MB")

        if self.calls_count > 1:
            lines.append(f"  Calls: {self.calls_count}")

        return "\n".join(lines)

    @staticmethod
    def _format_time(seconds: float) -> str:
        """Format time in appropriate units."""
        if seconds < 1e-6:
            return f"{seconds * 1e9:.2f} ns"
        elif seconds < 1e-3:
            return f"{seconds * 1e6:.2f} μs"
        elif seconds < 1:
            return f"{seconds * 1e3:.2f} ms"
        else:
            return f"{seconds:.2f} s"

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary."""
        return {
            "function_name": self.function_name,
            "execution_time": self.execution_time,
            "memory_peak_mb": self.memory_peak,
            "memory_current_mb": self.memory_current,
            "calls_count": self.calls_count,
            "metadata": self.metadata,
        }


class TimeProfiler:
    """Context manager for time profiling."""

    def __init__(self, name: str = "Operation"):
        self.name = name
        self.start_time: Optional[float] = None
        self.end_time: Optional[float] = None
        self.result: Optional[ProfileResult] = None

    def __enter__(self) -> "TimeProfiler":
        """Start timing."""
        self.start_time = time.perf_counter()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb) -> None:
        """Stop timing and store result."""
        self.end_time = time.perf_counter()
        execution_time = self.end_time - self.start_time

        self.result = ProfileResult(
            function_name=self.name, execution_time=execution_time
        )

    @property
    def elapsed(self) -> Optional[float]:
        """Get elapsed time."""
        if self.start_time is None:
            return None
        if self.end_time is None:
            return time.perf_counter() - self.start_time
        return self.end_time - self.start_time


class MemoryProfiler:
    """Context manager for memory profiling."""

    def __init__(self, name: str = "Operation"):
        self.name = name
        self.result: Optional[ProfileResult] = None
        self._tracking = False

    def __enter__(self) -> "MemoryProfiler":
        """Start memory tracking."""
        tracemalloc.start()
        self._tracking = True
        return self

    def __exit__(self, exc_type, exc_val, exc_tb) -> None:
        """Stop memory tracking and store result."""
        if self._tracking:
            current, peak = tracemalloc.get_traced_memory()
            tracemalloc.stop()
            self._tracking = False

            self.result = ProfileResult(
                function_name=self.name,
                memory_peak=peak / 1024 / 1024,  # Convert to MB
                memory_current=current / 1024 / 1024,
            )


class Profiler:
    """Combined context manager for both time and memory profiling."""

    def __init__(self, name: str = "Operation", track_memory: bool = True):
        self.name = name
        self.track_memory = track_memory
        self.start_time: Optional[float] = None
        self.end_time: Optional[float] = None
        self.result: Optional[ProfileResult] = None
        self._tracking = False

    def __enter__(self) -> "Profiler":
        """Start profiling."""
        self.start_time = time.perf_counter()

        if self.track_memory:
            tracemalloc.start()
            self._tracking = True

        return self

    def __exit__(self, exc_type, exc_val, exc_tb) -> None:
        """Stop profiling and store result."""
        self.end_time = time.perf_counter()
        execution_time = self.end_time - self.start_time

        memory_peak = None
        memory_current = None

        if self._tracking:
            current, peak = tracemalloc.get_traced_memory()
            tracemalloc.stop()
            self._tracking = False
            memory_peak = peak / 1024 / 1024  # Convert to MB
            memory_current = current / 1024 / 1024

        self.result = ProfileResult(
            function_name=self.name,
            execution_time=execution_time,
            memory_peak=memory_peak,
            memory_current=memory_current,
        )

    @property
    def elapsed(self) -> Optional[float]:
        """Get elapsed time."""
        if self.start_time is None:
            return None
        if self.end_time is None:
            return time.perf_counter() - self.start_time
        return self.end_time - self.start_time
