from __future__ import annotations

import json
import os
import subprocess
import sys
import time
import uuid
from pathlib import Path

import click
import requests

from state import DeviceState, list_all, load, remove, save

PID_DIR = Path(__file__).parent / "pids"
PID_DIR.mkdir(exist_ok=True)


def _pid_file(alias: str) -> Path:
    return PID_DIR / f"{alias}.pid"


def _is_running(alias: str) -> bool:
    pid_file = _pid_file(alias)
    if not pid_file.exists():
        return False
    pid = int(pid_file.read_text().strip())
    try:
        os.kill(pid, 0)
        return True
    except OSError:
        pid_file.unlink(missing_ok=True)
        return False


def _start_emulator(alias: str, host: str = "0.0.0.0", port: int | None = None) -> None:
    state = load(alias)
    if state is None:
        click.echo(f"Device '{alias}' not found.", err=True)
        sys.exit(1)

    if _is_running(alias):
        click.echo(f"Device '{alias}' is already running (PID {int(_pid_file(alias).read_text().strip())}).")
        sys.exit(1)

    cmd = [sys.executable, str(Path(__file__).parent / "emulator.py"), "--alias", alias, "--host", host]
    if port is not None:
        cmd += ["--port", str(port)]

    proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    time.sleep(0.5)
    if proc.poll() is not None:
        click.echo("Emulator failed to start.", err=True)
        sys.exit(1)

    _pid_file(alias).write_text(str(proc.pid))
    actual_port = port or state.port
    click.echo(f"Started '{alias}' on http://{host}:{actual_port} (PID {proc.pid})")


def _stop_emulator(alias: str) -> None:
    pid_file = _pid_file(alias)
    if not pid_file.exists():
        click.echo(f"Device '{alias}' is not running.")
        sys.exit(1)

    pid = int(pid_file.read_text().strip())
    try:
        os.kill(pid, 9)
        click.echo(f"Stopped '{alias}' (PID {pid}).")
    except ProcessLookupError:
        click.echo(f"Process {pid} not found, cleaning up.")
    finally:
        pid_file.unlink(missing_ok=True)


@click.group()
def cli():
    """Virtual Smart Home Device Manager."""
    pass


@cli.command()
@click.option("--type", "device_type", required=True, type=click.Choice(["relay", "button"]))
@click.option("--alias", required=True, help="Unique alias (e.g. virt_relay)")
@click.option("--name", default=None, help="Human-readable name")
@click.option("--channels", default=4, type=int, help="Number of channels")
@click.option("--port", default=9001, type=int, help="HTTP port for emulator")
@click.option("--server-url", default="http://localhost", help="Server URL to send events to")
@click.option("--ip", default="127.0.0.1", help="IP address reported in /about")
def create(device_type, alias, name, channels, port, server_url, ip):
    """Create a new virtual device state file."""
    if load(alias) is not None:
        click.echo(f"Device '{alias}' already exists. Use a different alias or remove it first.", err=True)
        sys.exit(1)

    device_id = f"virt-{uuid.uuid4().hex[:12]}"
    mac = f"VIRTUAL:{uuid.uuid4().hex[:6].upper()}:{uuid.uuid4().hex[:6].upper()}"
    state = DeviceState(
        alias=alias,
        device_type=device_type,
        device_id=device_id,
        device_name=name or alias,
        port=port,
        server_url=server_url,
        mac_address=mac,
        channels=[{} for _ in range(channels)],
    )
    state.channels_schema = []
    for ch in range(8):
        base = ch * 4
        state.channels_schema.extend([base, ch, 255, 0])
    save(state)
    click.echo(f"Created {device_type} '{alias}' (ID {device_id}) on port {port}.")
    click.echo(f"State file: devices/{alias}.json")


@cli.command()
@click.option("--alias", required=True)
@click.option("--host", default="0.0.0.0")
@click.option("--port", type=int, default=None)
def start(alias, host, port):
    """Start the emulator for a device."""
    _start_emulator(alias, host, port)


@cli.command()
@click.option("--alias", required=True)
def stop(alias):
    """Stop the emulator for a device."""
    _stop_emulator(alias)


@cli.command("list")
def list_devices():
    """List all virtual devices and their running status."""
    aliases = list_all()
    if not aliases:
        click.echo("No virtual devices found. Use 'create' to make one.")
        return

    click.echo(f"{'Alias':<20} {'Type':<10} {'Port':<8} {'Status'}")
    click.echo("-" * 50)
    for alias in aliases:
        state = load(alias)
        running = "running" if _is_running(alias) else "stopped"
        click.echo(f"{alias:<20} {state.device_type:<10} {state.port:<8} {running}")


@cli.command()
@click.option("--alias", required=True)
def status(alias):
    """Show device state and current channels."""
    state = load(alias)
    if state is None:
        click.echo(f"Device '{alias}' not found.", err=True)
        sys.exit(1)

    click.echo(json.dumps(state.to_dict(), indent=2, ensure_ascii=False))


@cli.command("click")
@click.option("--alias", required=True)
@click.option("--channel", default=0, type=int)
def click_cmd(alias, channel):
    """Simulate a button click (for button-type devices)."""
    state = load(alias)
    if state is None:
        click.echo(f"Device '{alias}' not found.", err=True)
        sys.exit(1)

    url = f"http://127.0.0.1:{state.port}/simulate-event"
    try:
        resp = requests.post(url, json={"event_name": "click", "channel": channel}, timeout=5)
        click.echo(resp.json())
    except requests.RequestException as e:
        click.echo(f"Failed to reach emulator: {e}", err=True)
        sys.exit(1)


@cli.command()
@click.option("--alias", required=True)
@click.option("--server-url", default="http://localhost", help="PHP server base URL")
def register(alias, server_url):
    """Register the virtual device in the PHP server (setup flow)."""
    state = load(alias)
    if state is None:
        click.echo(f"Device '{alias}' not found.", err=True)
        sys.exit(1)

    # Step 1: call /about to verify setup mode
    about_url = f"http://127.0.0.1:{state.port}/about"
    try:
        resp = requests.get(about_url, timeout=5)
        about = resp.json()
    except requests.RequestException as e:
        click.echo(f"Emulator not reachable at {about_url}: {e}", err=True)
        sys.exit(1)

    if about.get("status") != "setup":
        click.echo("Device is not in setup mode. Reset it first.", err=True)
        sys.exit(1)

    # Step 2: register in PHP server via API
    setup_url = f"{server_url.rstrip('/')}/api/v1/devices/setup/new-device"
    payload = {
        "device_ip": f"127.0.0.1:{state.port}",
        "alias": alias,
        "name": state.device_name,
        "description": "Virtual device (emulator)",
    }
    try:
        resp = requests.post(setup_url, json=payload, timeout=10)
        result = resp.json()
    except requests.RequestException as e:
        click.echo(f"Server request failed: {e}", err=True)
        sys.exit(1)

    if result.get("status") == "ok":
        click.echo(f"Device '{alias}' registered successfully.")
        click.echo(json.dumps(result.get("data", {}).get("device", {}), indent=2, ensure_ascii=False))
    else:
        click.echo(f"Registration failed: {result}", err=True)
        sys.exit(1)


@cli.command("remove")
@click.option("--alias", required=True)
def remove_cmd(alias):
    """Remove a virtual device state file."""
    if _is_running(alias):
        click.echo(f"Device '{alias}' is running. Stop it first.", err=True)
        sys.exit(1)

    if remove(alias):
        click.echo(f"Removed device '{alias}'.")
    else:
        click.echo(f"Device '{alias}' not found.", err=True)
        sys.exit(1)


if __name__ == "__main__":
    cli()
