from __future__ import annotations

import json
from pathlib import Path
import subprocess
import time
from typing import Any
from urllib.error import URLError
from urllib.request import urlopen

from autosana_cli.models import APPIUM_HEALTH_TIMEOUT_S, Device, Platform
from autosana_cli.processes import detach_kwargs


def appium_status(port: int) -> dict[str, Any] | None:
    try:
        with urlopen(f"http://127.0.0.1:{port}/status", timeout=1) as response:
            payload = json.loads(response.read().decode("utf-8"))
    except (URLError, TimeoutError, json.JSONDecodeError):
        return None
    return payload


def is_appium_ready(port: int) -> bool:
    payload = appium_status(port)
    if not payload:
        return False
    value = payload.get("value", {})
    return bool(value.get("ready", payload.get("ready", False)))


def appium_version(port: int) -> str | None:
    payload = appium_status(port)
    if not payload:
        return None
    value = payload.get("value", {})
    version = value.get("build", {}).get("version") or value.get("version")
    if version is None:
        version = payload.get("version")
    return str(version) if version else None


def default_capabilities(device: Device, aux_port: int) -> dict[str, Any]:
    if device.platform is Platform.IOS:
        return {
            "platformName": "iOS",
            "appium:automationName": "XCUITest",
            "appium:udid": device.udid,
            "appium:wdaLocalPort": aux_port,
        }
    return {
        "platformName": "Android",
        "appium:automationName": "UiAutomator2",
        "appium:udid": device.udid,
        "appium:systemPort": aux_port,
    }


def start_appium(
    *,
    port: int,
    device: Device,
    aux_port: int,
    log_path: Path,
    detach: bool,
) -> subprocess.Popen[str]:
    from autosana_cli import runtime

    log_path.parent.mkdir(parents=True, exist_ok=True)
    log_handle = log_path.open("a", encoding="utf-8")
    appium_bin = runtime.find_appium() or "appium"
    env = runtime.runtime_env()
    command = [
        str(appium_bin),
        "-p",
        str(port),
        "--default-capabilities",
        json.dumps(default_capabilities(device, aux_port)),
    ]
    if device.platform is Platform.ANDROID:
        command.extend(["--allow-insecure", "uiautomator2:adb_shell"])
    return subprocess.Popen(
        command,
        stdout=log_handle,
        stderr=log_handle,
        text=True,
        env=env,
        **detach_kwargs(detach),
    )


def wait_for_appium_ready(
    port: int,
    proc: subprocess.Popen[str],
    *,
    timeout: float = APPIUM_HEALTH_TIMEOUT_S,
) -> None:
    deadline = time.time() + timeout
    while time.time() < deadline:
        if proc.poll() is not None:
            raise RuntimeError(f"Appium exited before becoming ready on port {port}")
        if is_appium_ready(port):
            return
        time.sleep(0.5)
    raise RuntimeError(f"Appium failed to become ready within {int(timeout)}s")
