"""
Configuration for ESPal daemon.

Handles loading configuration from:
1. Default values
2. User config file (~/.config/esppal/config.yaml)
3. Project config (platformio.ini, etc.)
4. Command-line arguments
"""

import os
import yaml
import logging
from dataclasses import dataclass, field
from typing import Optional
from pathlib import Path


logger = logging.getLogger(__name__)


# Default configuration values
DEFAULT_BUFFER_SIZE_MB = 50
DEFAULT_IDLE_TIMEOUT_MINUTES = 30
DEFAULT_BAUDRATE = 115200


@dataclass
class DaemonConfig:
    """Configuration for the ESPal daemon."""
    buffer_size_bytes: int = DEFAULT_BUFFER_SIZE_MB * 1024 * 1024
    idle_timeout_seconds: float = DEFAULT_IDLE_TIMEOUT_MINUTES * 60
    baudrate: int = DEFAULT_BAUDRATE
    vid: Optional[str] = None
    pid: Optional[str] = None
    device: Optional[str] = None
    project_dir: Optional[str] = None

    @property
    def vid_pid(self) -> Optional[str]:
        """Return VID:PID string if both are set."""
        if self.vid and self.pid:
            return f"{self.vid}:{self.pid}"
        return None

    @classmethod
    def from_dict(cls, data: dict) -> "DaemonConfig":
        """Create config from dictionary."""
        config = cls()

        daemon_section = data.get("daemon", {})

        if "buffer_size_mb" in daemon_section:
            config.buffer_size_bytes = int(daemon_section["buffer_size_mb"]) * 1024 * 1024

        if "idle_timeout_minutes" in daemon_section:
            config.idle_timeout_seconds = float(daemon_section["idle_timeout_minutes"]) * 60

        defaults_section = data.get("defaults", {})

        if "baudrate" in defaults_section:
            config.baudrate = int(defaults_section["baudrate"])

        return config


def get_config_path() -> Path:
    """Get the user config file path."""
    xdg_config = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))
    return Path(xdg_config) / "esppal" / "config.yaml"


def load_user_config() -> dict:
    """Load user configuration from file."""
    config_path = get_config_path()

    if not config_path.exists():
        return {}

    try:
        with open(config_path, "r") as f:
            data = yaml.safe_load(f)
            return data if data else {}
    except Exception as e:
        logger.warning(f"Failed to load config from {config_path}: {e}")
        return {}


def save_user_config(data: dict) -> bool:
    """Save user configuration to file."""
    config_path = get_config_path()

    try:
        config_path.parent.mkdir(parents=True, exist_ok=True)
        with open(config_path, "w") as f:
            yaml.safe_dump(data, f, default_flow_style=False)
        return True
    except Exception as e:
        logger.error(f"Failed to save config to {config_path}: {e}")
        return False


def load_config(
    vid: Optional[str] = None,
    pid: Optional[str] = None,
    device: Optional[str] = None,
    project_dir: Optional[str] = None,
    buffer_size_mb: Optional[int] = None,
    idle_timeout_minutes: Optional[float] = None,
    baudrate: Optional[int] = None
) -> DaemonConfig:
    """
    Load configuration with overrides.

    Priority (highest to lowest):
    1. Explicit arguments
    2. User config file
    3. Default values

    Args:
        vid: USB Vendor ID
        pid: USB Product ID
        device: Device path
        project_dir: Project directory
        buffer_size_mb: Buffer size in MB
        idle_timeout_minutes: Idle timeout in minutes
        baudrate: Serial baudrate

    Returns:
        DaemonConfig instance
    """
    # Start with user config
    user_data = load_user_config()
    config = DaemonConfig.from_dict(user_data)

    # Apply explicit overrides
    if buffer_size_mb is not None:
        config.buffer_size_bytes = buffer_size_mb * 1024 * 1024

    if idle_timeout_minutes is not None:
        config.idle_timeout_seconds = idle_timeout_minutes * 60

    if baudrate is not None:
        config.baudrate = baudrate

    if vid is not None:
        config.vid = vid

    if pid is not None:
        config.pid = pid

    if device is not None:
        config.device = device

    if project_dir is not None:
        config.project_dir = project_dir

    return config


def parse_vidpid(vidpid: str) -> tuple[str, str]:
    """
    Parse a VID:PID string.

    Args:
        vidpid: String in format "vid:pid" (e.g., "10c4:ea60")

    Returns:
        Tuple of (vid, pid)

    Raises:
        ValueError: If format is invalid
    """
    if ":" not in vidpid:
        raise ValueError(f"Invalid VID:PID format: {vidpid} (expected 'vid:pid')")

    parts = vidpid.split(":", 1)
    if len(parts) != 2:
        raise ValueError(f"Invalid VID:PID format: {vidpid}")

    vid, pid = parts
    if not vid or not pid:
        raise ValueError(f"Invalid VID:PID format: {vidpid} (empty component)")

    return vid, pid


def get_socket_path(vid: Optional[str] = None, pid: Optional[str] = None,
                    device: Optional[str] = None) -> str:
    """
    Get the Unix socket path for a daemon instance.

    Args:
        vid: USB Vendor ID
        pid: USB Product ID
        device: Device path (fallback if no VID:PID)

    Returns:
        Socket path string
    """
    if vid and pid:
        return f"/tmp/esppal-{vid}:{pid}.sock"
    elif device:
        # Hash the device path for a stable name
        import hashlib
        device_hash = hashlib.md5(device.encode()).hexdigest()[:12]
        return f"/tmp/esppal-{device_hash}.sock"
    else:
        return "/tmp/esppal-default.sock"


def get_pid_path(vid: Optional[str] = None, pid: Optional[str] = None,
                 device: Optional[str] = None) -> str:
    """
    Get the PID file path for a daemon instance.

    Args:
        vid: USB Vendor ID
        pid: USB Product ID
        device: Device path (fallback if no VID:PID)

    Returns:
        PID file path string
    """
    socket_path = get_socket_path(vid, pid, device)
    return socket_path.replace(".sock", ".pid")
