import re
import subprocess
import threading
import time
from flask import Blueprint, jsonify, request, abort
from auth import login_required

bp = Blueprint('bluetooth', __name__, url_prefix='/api/bluetooth')

_BT_ADDR_RE = re.compile(r'^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$')

_bt_scanning  = False
_bt_scan_lock = threading.Lock()
_bt_scan_proc = None


def _bt_run(*args, timeout=15):
    cmd = ['bluetoothctl'] + list(args)
    try:
        r = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
        return r.returncode, r.stdout.strip()
    except subprocess.TimeoutExpired:
        return 1, 'timeout'
    except Exception as e:
        return 1, str(e)


def _bt_status():
    rc, out = _bt_run('show')
    powered = False
    name = ''
    addr = ''
    discovering = False
    for line in out.splitlines():
        s = line.strip()
        if s.startswith('Powered:'):       powered     = 'yes' in s
        elif s.startswith('Name:'):        name        = s.split(':', 1)[1].strip()
        elif s.startswith('Discovering:'): discovering = 'yes' in s
    m = re.search(r'([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}', out.split('\n')[0] if out else '')
    if m:
        addr = m.group(0)
    return {
        'powered':     powered,
        'name':        name,
        'address':     addr,
        'discovering': discovering or _bt_scanning,
        'available':   rc == 0,
    }


def _bt_device_info(addr):
    rc, info = _bt_run('info', addr)
    connected = paired = trusted = False
    dev_name = ''
    dev_type = ''
    for line in info.splitlines():
        s = line.strip()
        if s.startswith('Name:'):        dev_name  = s.split(':', 1)[1].strip()
        elif s.startswith('Connected:'): connected = 'yes' in s
        elif s.startswith('Paired:'):    paired    = 'yes' in s
        elif s.startswith('Trusted:'):   trusted   = 'yes' in s
        elif s.startswith('Icon:'):
            icon = s.split(':', 1)[1].strip()
            if any(k in icon for k in ('audio', 'headphone', 'headset', 'speaker')):
                dev_type = 'audio'
            elif 'phone' in icon:
                dev_type = 'phone'
            elif 'input' in icon:
                dev_type = 'input'
            else:
                dev_type = icon
    return {
        'address': addr, 'name': dev_name,
        'connected': connected, 'paired': paired, 'trusted': trusted,
        'type': dev_type,
    }


def _bt_devices():
    rc, out = _bt_run('devices')
    result = []
    for line in out.splitlines():
        m = re.match(r'Device\s+([0-9A-Fa-f]{2}(?::[0-9A-Fa-f]{2}){5})\s+(.*)', line)
        if m:
            addr = m.group(1)
            name = m.group(2).strip()
            info = _bt_device_info(addr)
            if not info['name']:
                info['name'] = name
            result.append(info)
    return result


def _bt_scan_worker(duration=15):
    global _bt_scanning, _bt_scan_proc
    try:
        _bt_scan_proc = subprocess.Popen(
            ['bluetoothctl', 'scan', 'on'],
            stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
        )
        time.sleep(duration)
    finally:
        try:
            if _bt_scan_proc:
                _bt_scan_proc.terminate()
                _bt_scan_proc = None
        except Exception:
            pass
        _bt_run('scan', 'off', timeout=5)
        with _bt_scan_lock:
            _bt_scanning = False


@bp.route('/status')
@login_required
def api_bt_status():
    return jsonify(_bt_status())


@bp.route('/devices')
@login_required
def api_bt_devices():
    return jsonify({'devices': _bt_devices(), 'scanning': _bt_scanning})


@bp.route('/scan', methods=['POST'])
@login_required
def api_bt_scan():
    global _bt_scanning
    with _bt_scan_lock:
        if _bt_scanning:
            return jsonify({'ok': True, 'scanning': True})
        _bt_scanning = True
    t = threading.Thread(target=_bt_scan_worker, args=(15,), daemon=True)
    t.start()
    return jsonify({'ok': True, 'scanning': True})


@bp.route('/pair', methods=['POST'])
@login_required
def api_bt_pair():
    data = request.get_json()
    if not data or not data.get('address'):
        abort(400)
    addr = str(data['address'])
    if not _BT_ADDR_RE.match(addr):
        abort(400)
    _bt_run('trust', addr, timeout=5)
    rc, out = _bt_run('pair', addr, timeout=30)
    ok = rc == 0 or 'already paired' in out.lower() or 'already exists' in out.lower()
    if ok:
        _bt_run('connect', addr, timeout=15)
    return jsonify({'ok': ok, 'message': '' if ok else out.split('\n')[-1]})


@bp.route('/connect', methods=['POST'])
@login_required
def api_bt_connect():
    data = request.get_json()
    if not data or not data.get('address'):
        abort(400)
    addr = str(data['address'])
    if not _BT_ADDR_RE.match(addr):
        abort(400)
    rc, out = _bt_run('connect', addr, timeout=15)
    ok = rc == 0 or 'connected' in out.lower()
    return jsonify({'ok': ok, 'message': '' if ok else out.split('\n')[-1]})


@bp.route('/disconnect', methods=['POST'])
@login_required
def api_bt_disconnect():
    data = request.get_json()
    if not data or not data.get('address'):
        abort(400)
    addr = str(data['address'])
    if not _BT_ADDR_RE.match(addr):
        abort(400)
    _bt_run('disconnect', addr, timeout=10)
    return '', 204


@bp.route('/device/<path:address>', methods=['DELETE'])
@login_required
def api_bt_remove(address):
    if not _BT_ADDR_RE.match(address):
        abort(400)
    _bt_run('remove', address, timeout=10)
    return '', 204


@bp.route('/power', methods=['POST'])
@login_required
def api_bt_power():
    data = request.get_json() or {}
    on = bool(data.get('on', True))
    _bt_run('power', 'on' if on else 'off', timeout=5)
    return jsonify(_bt_status())
