"""Bluetooth management module for Pi Multi-FX Pedal.

Uses bluetoothctl (BlueZ) for all Bluetooth operations — no extra deps
on RPi OS or DietPi. Handles device scanning, pairing, unpairing, and
Bluetooth MIDI service management via systemd.
"""

from __future__ import annotations

import asyncio
import logging
import re
import subprocess
import time
from pathlib import Path
from typing import Any, Optional

logger = logging.getLogger(__name__)

# ── Constants ─────────────────────────────────────────────────────────────────

BT_MIDI_SERVICE_NAME = "pi-bt-midi"
BT_MIDI_SERVICE_PATH = Path(f"/etc/systemd/system/{BT_MIDI_SERVICE_NAME}.service")
BTCTL_TIMEOUT = 30  # seconds to wait for bluetoothctl commands

# ── Low-level bluetoothctl helpers ────────────────────────────────────────────


def _bluetoothctl(cmd: str, timeout: int = BTCTL_TIMEOUT) -> str:
    """Run a bluetoothctl command and return stdout.

    Uses the simple command form: bluetoothctl <cmd>
    """
    try:
        result = subprocess.run(
            ["bluetoothctl", cmd],
            capture_output=True, text=True, timeout=timeout,
        )
        return result.stdout
    except FileNotFoundError:
        logger.warning("bluetoothctl not found — BlueZ not installed")
        return ""
    except subprocess.TimeoutExpired:
        logger.warning("bluetoothctl command timed out: %s", cmd)
        return ""
    except Exception as e:
        logger.warning("bluetoothctl error: %s", e)
        return ""


def _bluetoothctl_interactive(commands: list[str], timeout: int = 15) -> str:
    """Run multiple commands in an interactive bluetoothctl session.

    Uses echo-pipe to feed commands into bluetoothctl.
    """
    try:
        full_cmd = " && ".join(
            f"echo '{c}'" for c in commands[:-1]
        ) + f" && echo '{commands[-1]}'"
        result = subprocess.run(
            ["bash", "-c", f"{full_cmd} | bluetoothctl"],
            capture_output=True, text=True, timeout=timeout,
        )
        return result.stdout
    except Exception as e:
        logger.warning("bluetoothctl interactive error: %s", e)
        return ""


def _bt_available() -> bool:
    """Check if Bluetooth hardware is available and not blocked."""
    try:
        result = subprocess.run(
            ["bluetoothctl", "show"],
            capture_output=True, text=True, timeout=10,
        )
        if result.returncode != 0:
            return False
        # Check if powered on
        return "Powered: yes" in result.stdout
    except FileNotFoundError:
        return False


# ── Core Bluetooth operations ─────────────────────────────────────────────────


def bt_status() -> dict[str, Any]:
    """Get Bluetooth adapter status.

    Returns dict with: available (bool), powered (bool), name (str),
    discoverable (bool), pairable (bool), address (str|None).
    """
    output = _bluetoothctl("show", timeout=10)
    if not output:
        return {
            "available": False, "powered": False,
            "name": None, "discoverable": False, "pairable": False,
            "address": None,
        }

    available = True
    powered = "Powered: yes" in output
    name = None
    discoverable = "Discoverable: yes" in output
    pairable = "Pairable: yes" in output
    address = None

    for line in output.split("\n"):
        line = line.strip()
        if line.startswith("Name:"):
            name = line.split(":", 1)[1].strip()
        elif line.startswith("Controller"):
            parts = line.split()
            if len(parts) >= 2 and ":" in parts[1]:
                address = parts[1]

    return {
        "available": available,
        "powered": powered,
        "name": name or "pi-pedal",
        "discoverable": discoverable,
        "pairable": pairable,
        "address": address,
    }


def bt_power(on: bool = True) -> dict[str, Any]:
    """Power Bluetooth adapter on or off.

    Args:
        on: True to power on, False to power off.

    Returns:
        dict with ok (bool) key.
    """
    cmd = "power on" if on else "power off"
    output = _bluetoothctl(cmd, timeout=15)
    # Verify
    time.sleep(0.5)
    status = bt_status()
    success = status.get("powered") == on
    logger.info("Bluetooth power %s: %s", "on" if on else "off", "OK" if success else "FAIL")
    return {"ok": success}


def bt_discoverable(on: bool = True) -> dict[str, Any]:
    """Set Bluetooth discoverable mode.

    Args:
        on: True to make discoverable, False to hide.

    Returns:
        dict with ok (bool) key.
    """
    cmd = "discoverable on" if on else "discoverable off"
    _bluetoothctl(cmd, timeout=15)
    time.sleep(0.5)
    status = bt_status()
    success = status.get("discoverable") == on
    return {"ok": success}


def bt_scan(timeout: int = 12) -> dict[str, Any]:
    """Scan for discoverable Bluetooth devices.

    Uses bluetoothctl scan on, waits, then parses the device list.

    Args:
        timeout: Seconds to scan (default 12).

    Returns:
        dict with ok (bool) and devices (list of dicts with address, name, rssi).
    """
    # Start scanning
    output = _bluetoothctl("scan on", timeout=timeout + 5)

    # Wait for devices to appear
    time.sleep(timeout)

    # Stop scanning
    _bluetoothctl("scan off", timeout=5)

    # Get the device list
    devices_output = _bluetoothctl("devices", timeout=5)
    devices: list[dict[str, Any]] = []

    # Parse devices (format: Device AA:BB:CC:DD:EE:FF Device Name)
    device_pattern = re.compile(r"Device\s+([0-9A-Fa-f:]+)\s+(.+)$")
    for line in devices_output.split("\n"):
        match = device_pattern.match(line.strip())
        if match:
            addr = match.group(1).upper()
            name = match.group(2).strip()
            # Try to find RSSI info from the scan output
            rssi = None
            for scan_line in output.split("\n"):
                if addr in scan_line and "RSSI" in scan_line:
                    rssi_match = re.search(r"RSSI[:\s]+(-?\d+)", scan_line)
                    if rssi_match:
                        rssi = int(rssi_match.group(1))
                    break
            devices.append({
                "address": addr,
                "name": name if name else "Unknown",
                "rssi": rssi,
            })

    # Sort by RSSI (strongest first)
    devices.sort(key=lambda d: d["rssi"] if d["rssi"] is not None else -999, reverse=True)

    logger.info("BT scan found %d devices", len(devices))
    return {"ok": True, "devices": devices}


def bt_paired_devices() -> list[dict[str, Any]]:
    """List currently paired Bluetooth devices.

    Returns list of dicts with address, name, connected, trusted.
    """
    output = _bluetoothctl("paired-devices", timeout=10)
    devices: list[dict[str, Any]] = []

    # Get connected devices to mark them
    info_output = _bluetoothctl("info", timeout=10)

    pattern = re.compile(r"Device\s+([0-9A-Fa-f:]+)\s+(.+)$")
    for line in output.split("\n"):
        match = pattern.match(line.strip())
        if match:
            addr = match.group(1).upper()
            name = match.group(2).strip()
            # Check if connected
            connected = addr in info_output and "Connected: yes" in info_output
            devices.append({
                "address": addr,
                "name": name,
                "connected": connected,
                "trusted": False,  # Would need to check each device
            })

    return devices


def bt_pair(address: str, timeout: int = 30) -> dict[str, Any]:
    """Pair with a Bluetooth device.

    Args:
        address: Device MAC address (AA:BB:CC:DD:EE:FF).
        timeout: Max seconds to wait for pairing.

    Returns:
        dict with ok (bool) and error (str|None).
    """
    if not address or ":" not in address:
        return {"ok": False, "error": "Invalid MAC address"}

    address = address.upper()

    # Trust first (helps with pairing)
    _bluetoothctl(f"trust {address}", timeout=10)

    # Attempt pairing
    output = _bluetoothctl(f"pair {address}", timeout=timeout)

    if "Failed to pair" in output or "error" in output.lower():
        logger.warning("Pairing failed with %s", address)
        return {"ok": False, "error": f"Pairing failed with {address}"}

    # Try to connect
    _bluetoothctl(f"connect {address}", timeout=15)
    time.sleep(1)

    logger.info("Paired with %s", address)
    return {"ok": True}


def bt_unpair(address: str) -> dict[str, Any]:
    """Unpair/remove a Bluetooth device.

    Args:
        address: Device MAC address.

    Returns:
        dict with ok (bool).
    """
    if not address or ":" not in address:
        return {"ok": False, "error": "Invalid MAC address"}

    address = address.upper()

    # Disconnect first
    _bluetoothctl(f"disconnect {address}", timeout=10)
    time.sleep(0.3)

    # Remove
    output = _bluetoothctl(f"remove {address}", timeout=10)
    success = "not available" not in output

    if success:
        logger.info("Unpaired %s", address)
    return {"ok": success}


def bt_connect(address: str) -> dict[str, Any]:
    """Connect to a paired Bluetooth device.

    Args:
        address: Device MAC address.

    Returns:
        dict with ok (bool) and error (str|None).
    """
    if not address or ":" not in address:
        return {"ok": False, "error": "Invalid MAC address"}

    address = address.upper()
    output = _bluetoothctl(f"connect {address}", timeout=20)

    if "Connection successful" in output or "successful" in output.lower():
        return {"ok": True}
    else:
        logger.warning("Connection to %s failed", address)
        return {"ok": False, "error": "Connection failed"}


def bt_disconnect(address: str) -> dict[str, Any]:
    """Disconnect a Bluetooth device.

    Args:
        address: Device MAC address.

    Returns:
        dict with ok (bool).
    """
    if not address or ":" not in address:
        return {"ok": False, "error": "Invalid MAC address"}

    address = address.upper()
    _bluetoothctl(f"disconnect {address}", timeout=10)
    return {"ok": True}


# ── Bluetooth MIDI service management ─────────────────────────────────────────


def bt_midi_status() -> dict[str, Any]:
    """Check Bluetooth MIDI service status.

    Returns dict with:
        enabled (bool): service is enabled (starts on boot).
        running (bool): service is currently running.
    """
    # Check if service is enabled
    enabled = False
    running = False

    try:
        result = subprocess.run(
            ["systemctl", "is-enabled", BT_MIDI_SERVICE_NAME],
            capture_output=True, text=True, timeout=10,
        )
        enabled = result.stdout.strip() == "enabled"
    except Exception:
        pass

    try:
        result = subprocess.run(
            ["systemctl", "is-active", BT_MIDI_SERVICE_NAME],
            capture_output=True, text=True, timeout=10,
        )
        running = result.stdout.strip() == "active"
    except Exception:
        pass

    return {
        "enabled": enabled,
        "running": running,
    }


def bt_midi_enable() -> dict[str, Any]:
    """Enable and start the Bluetooth MIDI service.

    The service must be installed first (via scripts/setup-bt-midi.sh).

    Returns dict with ok (bool) and error (str|None).
    """
    try:
        subprocess.run(
            ["sudo", "systemctl", "enable", BT_MIDI_SERVICE_NAME],
            capture_output=True, text=True, timeout=15, check=True,
        )
        subprocess.run(
            ["sudo", "systemctl", "start", BT_MIDI_SERVICE_NAME],
            capture_output=True, text=True, timeout=15, check=True,
        )
        logger.info("Bluetooth MIDI service enabled and started")
        return {"ok": True}
    except subprocess.CalledProcessError as e:
        logger.warning("Failed to enable BT MIDI: %s", e.stderr if e.stderr else str(e))
        return {"ok": False, "error": e.stderr.strip() if e.stderr else str(e)}


def bt_midi_disable() -> dict[str, Any]:
    """Stop and disable the Bluetooth MIDI service.

    Returns dict with ok (bool).
    """
    try:
        subprocess.run(
            ["sudo", "systemctl", "stop", BT_MIDI_SERVICE_NAME],
            capture_output=True, text=True, timeout=15,
        )
        subprocess.run(
            ["sudo", "systemctl", "disable", BT_MIDI_SERVICE_NAME],
            capture_output=True, text=True, timeout=15,
        )
        logger.info("Bluetooth MIDI service stopped and disabled")
        return {"ok": True}
    except Exception as e:
        logger.warning("Failed to disable BT MIDI: %s", e)
        return {"ok": False}


def bt_midi_connected_devices() -> list[dict[str, Any]]:
    """List devices connected via Bluetooth MIDI.

    Checks which paired devices are currently connected and
    might be MIDI controllers.

    Returns list of dicts with address, name.
    """
    # Get the info for all paired devices
    paired = bt_paired_devices()
    # Filter to connected ones
    midi_devices = [d for d in paired if d.get("connected")]
    return midi_devices