from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
import os
from pathlib import Path
from typing import Any

STATE_VERSION = 1
BASE_APPIUM_PORT = 4723
BASE_IOS_AUX_PORT = 8100
BASE_ANDROID_AUX_PORT = 8200
APPIUM_HEALTH_TIMEOUT_S = 15
TUNNEL_URL_TIMEOUT_S = 30
DEFAULT_CLOUDFLARED_VERSION = "2026.3.0"
DEFAULT_NODE_VERSION = "24.0.0"
DEFAULT_APPIUM_VERSION = "3.1.0"
DEFAULT_XCUITEST_VERSION = "10.2.2"
DEFAULT_UIAUTOMATOR2_VERSION = "6.0.2"


class Platform(str, Enum):
    IOS = "ios"
    ANDROID = "android"


def autosana_home() -> Path:
    configured = os.environ.get("AUTOSANA_HOME")
    if configured:
        return Path(configured).expanduser().resolve()
    return (Path.home() / ".autosana").resolve()


def state_path() -> Path:
    return autosana_home() / "state.json"


def lock_path() -> Path:
    return autosana_home() / "state.lock"


def logs_dir() -> Path:
    return autosana_home() / "logs"


def bin_dir() -> Path:
    return autosana_home() / "bin"


def node_home() -> Path:
    return autosana_home() / "node" / f"v{DEFAULT_NODE_VERSION}"


def appium_home() -> Path:
    return autosana_home() / "appium"


def fix_lock_path() -> Path:
    return autosana_home() / "fix.lock"


def update_check_path() -> Path:
    return autosana_home() / "update-check.json"


def utc_now_iso() -> str:
    return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")


def build_session_id(platform: Platform, appium_port: int, started_at: str | None = None) -> str:
    timestamp = started_at or utc_now_iso()
    compact = timestamp.replace("-", "").replace(":", "")
    compact = compact.replace(".000000", "").replace("T", "T").replace("Z", "Z")
    return f"{platform.value}-{appium_port}-{compact}"


@dataclass(frozen=True)
class InstalledApp:
    bundle_id: str
    name: str

    def to_dict(self) -> dict[str, Any]:
        return {"bundle_id": self.bundle_id, "name": self.name}


@dataclass(frozen=True)
class Device:
    platform: Platform
    name: str
    udid: str
    booted: bool = True
    runtime: str | None = None


@dataclass
class Session:
    session_id: str
    platform: Platform
    device_name: str
    device_udid: str
    appium_port: int
    appium_pid: int
    tunnel_url: str
    tunnel_pid: int
    aux_port: int
    appium_log_path: str
    tunnel_log_path: str
    project_root: str
    started_at: str
    # Transient fields — populated at runtime for JSON output, not persisted to state.json
    installed_apps: list[InstalledApp] = field(default_factory=list)
    app_detection: str = "ok"  # "ok" | "none_found" | "failed"

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "Session":
        return cls(
            session_id=str(data["session_id"]),
            platform=Platform(str(data["platform"])),
            device_name=str(data["device_name"]),
            device_udid=str(data["device_udid"]),
            appium_port=int(data["appium_port"]),
            appium_pid=int(data["appium_pid"]),
            tunnel_url=str(data["tunnel_url"]),
            tunnel_pid=int(data["tunnel_pid"]),
            aux_port=int(data["aux_port"]),
            appium_log_path=str(data["appium_log_path"]),
            tunnel_log_path=str(data["tunnel_log_path"]),
            project_root=str(data["project_root"]),
            started_at=str(data["started_at"]),
        )

    def to_dict(self) -> dict[str, Any]:
        return {
            "session_id": self.session_id,
            "platform": self.platform.value,
            "device_name": self.device_name,
            "device_udid": self.device_udid,
            "appium_port": self.appium_port,
            "appium_pid": self.appium_pid,
            "tunnel_url": self.tunnel_url,
            "tunnel_pid": self.tunnel_pid,
            "aux_port": self.aux_port,
            "appium_log_path": self.appium_log_path,
            "tunnel_log_path": self.tunnel_log_path,
            "project_root": self.project_root,
            "started_at": self.started_at,
        }

    def ready_payload(self) -> dict[str, Any]:
        return {
            "status": "ready",
            "session_id": self.session_id,
            "platform": self.platform.value,
            "device": self.device_name,
            "device_udid": self.device_udid,
            "appium_port": self.appium_port,
            "tunnel_url": self.tunnel_url,
            "installed_apps": [a.to_dict() for a in self.installed_apps],
            "app_detection": self.app_detection,
            "logs": {
                "appium": self.appium_log_path,
                "tunnel": self.tunnel_log_path,
            },
        }


@dataclass(frozen=True)
class SessionHealth:
    session: Session
    appium_pid_alive: bool
    tunnel_pid_alive: bool
    appium_ready: bool
    issues: list[str] = field(default_factory=list)

    @property
    def status(self) -> str:
        if self.appium_pid_alive and self.tunnel_pid_alive and self.appium_ready:
            return "ready"
        if not self.appium_pid_alive or not self.tunnel_pid_alive:
            return "dead"
        return "degraded"

    def to_dict(self) -> dict[str, Any]:
        payload = self.session.ready_payload()
        payload.update(
            {
                "device_udid": self.session.device_udid,
                "aux_port": self.session.aux_port,
                "appium_pid": self.session.appium_pid,
                "tunnel_pid": self.session.tunnel_pid,
                "appium_log_path": self.session.appium_log_path,
                "tunnel_log_path": self.session.tunnel_log_path,
                "project_root": self.session.project_root,
                "started_at": self.session.started_at,
                "appium_pid_alive": self.appium_pid_alive,
                "tunnel_pid_alive": self.tunnel_pid_alive,
                "appium_ready": self.appium_ready,
                "status": self.status,
                "issues": list(self.issues),
            }
        )
        return payload


@dataclass(frozen=True)
class DoctorCheck:
    name: str
    ok: bool
    message: str
    fixable: bool = False
    fix_label: str = ""
    group: str = "General"

    def to_dict(self) -> dict[str, Any]:
        d: dict[str, Any] = {"name": self.name, "ok": self.ok, "message": self.message}
        if not self.ok:
            d["fixable"] = self.fixable
            if self.fix_label:
                d["fix_label"] = self.fix_label
        return d


@dataclass(frozen=True)
class DoctorReport:
    platform: Platform | None
    checks: list[DoctorCheck]

    @property
    def ok(self) -> bool:
        return all(check.ok for check in self.checks)

    def to_dict(self) -> dict[str, Any]:
        return {
            "status": "ok" if self.ok else "error",
            "platform": self.platform.value if self.platform else "all",
            "checks": [check.to_dict() for check in self.checks],
        }
