"""Utility functions for FWAuto."""

# Python 3.11+ has tomllib built-in, earlier versions need tomli
import json
import os
import re
import subprocess
import tomllib
from pathlib import Path
from typing import Any


def load_toml(file_path: str | Path) -> dict[str, Any]:
    """
    Load and parse a TOML file.

    Args:
        file_path: Path to the TOML file

    Returns:
        Parsed TOML data as dictionary

    Raises:
        FileNotFoundError: If the TOML file does not exist
        tomllib.TOMLDecodeError: If the TOML file is malformed
    """
    path = Path(file_path)

    if not path.exists():
        raise FileNotFoundError(f"TOML file not found: {file_path}")

    with open(path, "rb") as f:
        return tomllib.load(f)


def check_fwauto_config_exists(project_root: str | Path) -> bool:
    """
    Check if .fwauto/config.toml exists in the project root.

    This is used to determine whether to use Makefile-based build/flash
    or fallback to STM32-specific legacy implementation.

    Args:
        project_root: Path to the project root directory

    Returns:
        True if .fwauto/config.toml exists, False otherwise
    """
    config_path = Path(project_root) / ".fwauto" / "config.toml"
    return config_path.exists()


def get_fwauto_dir(project_root: str | Path) -> Path:
    """
    Get the .fwauto directory path for a project.

    Args:
        project_root: Path to the project root directory

    Returns:
        Path to the .fwauto directory
    """
    return Path(project_root) / ".fwauto"


def ensure_fwauto_dir(project_root: str | Path) -> Path:
    """
    Ensure the .fwauto directory exists, create if not.

    Args:
        project_root: Path to the project root directory

    Returns:
        Path to the .fwauto directory
    """
    fwauto_dir = get_fwauto_dir(project_root)
    fwauto_dir.mkdir(parents=True, exist_ok=True)
    return fwauto_dir


def execute_make_command(
    makefile_path: Path,
    target: str,
    project_root: Path,
    timeout: int = 60,
    env_vars: dict[str, str] | None = None,
) -> tuple[int, str]:
    """
    Execute make command with real-time output and cross-platform compatibility.

    Args:
        makefile_path: Path to Makefile
        target: Make target to execute (e.g., "build", "flash")
        project_root: Project root directory
        timeout: Command timeout in seconds (default: 60)
        env_vars: Additional environment variables to pass to the process

    Returns:
        Tuple of (returncode, full_output)

    Raises:
        subprocess.TimeoutExpired: If command exceeds timeout
    """
    from .logging_config import get_logger

    makefile_dir = makefile_path.parent
    cmd = ["make", target, f"PROJECT_ROOT={project_root}"]

    logger = get_logger(__name__)
    logger.info(f"Executing: {' '.join(cmd)} in {makefile_dir}")

    # 準備環境變數
    env = os.environ.copy()
    if env_vars:
        env.update(env_vars)
        logger.info(f"Additional env vars: {list(env_vars.keys())}")

    # 準備 Popen 參數
    popen_kwargs = {
        "stdout": subprocess.PIPE,
        "stderr": subprocess.STDOUT,  # 合併 stderr 到 stdout
        "text": True,
        "encoding": "utf-8",
        "errors": "replace",
        "cwd": str(makefile_dir),
        "bufsize": 1,  # Line buffered
        "env": env,  # 新增環境變數
    }

    # Windows 平台：不使用特殊 creationflags（經測試，問題在 Makefile 的 -j0 參數）
    # 現在 Makefile 已修正，可以正常捕獲輸出

    # 執行命令
    process = subprocess.Popen(cmd, **popen_kwargs)

    # 使用 communicate 配合 timeout（一次性獲取所有輸出）
    print("")  # 空行分隔
    try:
        stdout_data, _ = process.communicate(timeout=timeout)
        output_lines = stdout_data.splitlines(keepends=True) if stdout_data else []

        # 輸出到 console
        for line in output_lines:
            print(line, end="", flush=True)
    except subprocess.TimeoutExpired:
        process.kill()
        stdout_data, _ = process.communicate()  # 收集已有的輸出
        output_lines = stdout_data.splitlines(keepends=True) if stdout_data else []
        raise subprocess.TimeoutExpired(cmd=cmd, timeout=timeout, output="".join(output_lines))
    except Exception as e:
        process.kill()
        process.communicate()  # 清理
        raise RuntimeError(f"Error executing command: {e}") from e
    finally:
        print("")  # 空行分隔

    # 組合完整輸出
    full_output = f"Command: {' '.join(cmd)}\n"
    full_output += f"Working directory: {makefile_dir}\n"
    full_output += f"Exit code: {process.returncode}\n"
    full_output += "=" * 60 + "\n\n"
    full_output += "".join(output_lines)

    # Log the full output to file
    logger.info(f"Make output:\n{full_output}")

    return process.returncode, full_output


def execute_python_script(
    script_path: Path,
    project_root: Path,
    timeout: int = 60,
    env_vars: dict[str, str] | None = None,
) -> tuple[int, str]:
    """
    執行 Python 腳本（統一使用 python3）。

    Args:
        script_path: Python 腳本路徑
        project_root: 專案根目錄
        timeout: 執行 timeout（秒）
        env_vars: 額外環境變數

    Returns:
        Tuple of (returncode, full_output)
    """
    from .logging_config import get_logger

    logger = get_logger(__name__)

    if not script_path.exists():
        error_msg = f"Script not found: {script_path}"
        logger.error(error_msg)
        return 1, error_msg

    cmd = ["python3", str(script_path)]
    logger.info(f"Executing: {' '.join(cmd)} in {project_root}")

    # 準備環境變數
    env = os.environ.copy()
    env["PROJECT_ROOT"] = str(project_root)
    if env_vars:
        env.update(env_vars)
        logger.info(f"Additional env vars: {list(env_vars.keys())}")

    try:
        result = subprocess.run(
            cmd,
            cwd=project_root,
            env=env,
            capture_output=True,
            text=True,
            timeout=timeout,
        )

        full_output = f"Command: {' '.join(cmd)}\n"
        full_output += f"Working directory: {project_root}\n"
        full_output += f"Exit code: {result.returncode}\n"
        full_output += "=" * 60 + "\n\n"
        full_output += result.stdout
        if result.stderr:
            full_output += "\n--- stderr ---\n"
            full_output += result.stderr

        logger.info(f"Script output:\n{full_output}")
        return result.returncode, full_output

    except subprocess.TimeoutExpired:
        error_msg = f"Script timeout after {timeout} seconds"
        logger.error(error_msg)
        return 1, error_msg

    except Exception as e:
        error_msg = f"Script execution error: {e}"
        logger.error(error_msg)
        return 1, error_msg


def detect_serial_port() -> str | None:
    """
    自動偵測 USB serial port（跨平台支援）。

    優先選擇有 VID/PID 的 USB 裝置（Arduino、ESP32 等開發板）。

    Returns:
        偵測到的 serial port 路徑，若無則返回 None
    """
    # 嘗試使用 arduino-cli（如果有安裝）
    port = _detect_via_arduino_cli()
    if port:
        return port

    # 備用：使用 pyserial 偵測
    port = _detect_via_pyserial()
    if port:
        return port

    return None


def _detect_via_arduino_cli() -> str | None:
    """使用 arduino-cli 偵測 serial port。"""
    try:
        result = subprocess.run(
            ["arduino-cli", "board", "list", "--format", "json"],
            capture_output=True,
            text=True,
            timeout=10,
        )
        if result.returncode != 0:
            return None

        data = json.loads(result.stdout)
        detected_ports = data.get("detected_ports", [])

        # 優先選擇有 VID 的 USB 裝置
        for p in detected_ports:
            port_info = p.get("port", {})
            props = port_info.get("properties", {})
            if props.get("vid"):  # 有 VID 表示是 USB 裝置
                return port_info.get("address")

        # 備用：選擇第一個 USB serial port
        for p in detected_ports:
            port_info = p.get("port", {})
            label = port_info.get("protocol_label", "")
            if "USB" in label:
                return port_info.get("address")

        return None
    except (subprocess.TimeoutExpired, FileNotFoundError, json.JSONDecodeError):
        return None


def _detect_via_pyserial() -> str | None:
    """使用 pyserial 偵測 serial port。"""
    try:
        from serial.tools import list_ports

        ports = list_ports.comports()

        # 優先選擇有 VID/PID 的 USB 裝置
        for p in ports:
            if p.vid is not None:
                return p.device

        # 備用：選擇包含 USB 關鍵字的 port
        for p in ports:
            if "USB" in p.description.upper() or "usbserial" in p.device.lower():
                return p.device

        return None
    except ImportError:
        return None


def detect_environment() -> dict[str, str]:
    """
    偵測當前環境資訊，返回可用於變數替換的字典。

    Returns:
        包含環境資訊的字典，例如：
        {
            "SERIAL_PORT": "/dev/cu.usbserial-110",
            "PLATFORM": "darwin",
            "ARCH": "arm64",
        }
    """
    import platform

    env = {
        "PLATFORM": platform.system().lower(),
        "ARCH": platform.machine(),
    }

    # 偵測 serial port
    serial_port = detect_serial_port()
    if serial_port:
        env["SERIAL_PORT"] = serial_port

    return env


def expand_variables(text: str, variables: dict[str, str] | None = None) -> str:
    """
    展開字串中的變數（支援 ${VAR} 和 $VAR 格式）。

    變數來源優先順序：
    1. 傳入的 variables 字典
    2. 系統環境變數

    Args:
        text: 包含變數的字串
        variables: 額外的變數字典

    Returns:
        展開變數後的字串
    """
    if not text:
        return text

    # 合併變數來源
    all_vars = os.environ.copy()
    if variables:
        all_vars.update(variables)

    # 展開 ${VAR} 格式
    def replace_braced(match: re.Match) -> str:
        var_name = match.group(1)
        default: str = match.group(0)
        return all_vars.get(var_name, default)

    text = re.sub(r"\$\{([^}]+)\}", replace_braced, text)

    # 展開 $VAR 格式（只匹配字母數字和底線）
    def replace_simple(match: re.Match) -> str:
        var_name = match.group(1)
        default: str = match.group(0)
        return all_vars.get(var_name, default)

    text = re.sub(r"\$([A-Za-z_][A-Za-z0-9_]*)", replace_simple, text)

    return text
