#!/usr/bin/env python3
"""Run EtherGold stage/promote/rollback as topbox (ethergold-http is often root)."""
from __future__ import annotations

import os
import subprocess
import time
from pathlib import Path
from typing import Any, Dict, List, Optional

EG_ROOT = Path(os.environ.get("ETHERGOLD_ROOT", "/home/topbox/EtherGold"))
EG_SYNC = EG_ROOT / "sync"
TOPBOX_USER = os.environ.get("ETHERGOLD_USER", os.environ.get("DYZINEFRAME_USER", "topbox"))
STAGE_TIMEOUT_SEC = int(os.environ.get("EG_DEPLOY_STAGE_TIMEOUT_SEC", "180"))
PROMOTE_TIMEOUT_SEC = int(os.environ.get("EG_DEPLOY_PROMOTE_TIMEOUT_SEC", "180"))


def _read_text(path: Path) -> str:
    try:
        return path.read_text(encoding="utf-8", errors="replace").strip()
    except OSError:
        return ""


def _stamp_tuple(stamp: str):
    parts = []
    for tok in (stamp or "").strip().split("."):
        if not tok.isdigit():
            return None
        parts.append(int(tok))
    if not parts:
        return None
    while len(parts) < 4:
        parts.append(0)
    return tuple(parts[:4])


def _stamp_is_older(left: str, right: str) -> bool:
    a, b = _stamp_tuple(left), _stamp_tuple(right)
    if a is None or b is None:
        return False
    return a < b


def _cmd_prefix() -> List[str]:
    try:
        if hasattr(os, "geteuid") and os.geteuid() == 0:
            return ["runuser", "-u", TOPBOX_USER, "--"]
    except OSError:
        pass
    return []


def _first_script(*names: str) -> str:
    for name in names:
        if (EG_SYNC / name).is_file():
            return name
    return names[0]


def _staging_sync() -> Path:
    home = Path("/home") / TOPBOX_USER
    return Path(os.environ.get("ETHERGOLD_STAGING", str(home / "EtherGold-Staging"))) / "sync"


def _run_eg_script(
    script: str,
    extra_env: Optional[Dict[str, str]] = None,
    timeout: int = 120,
    prefer_staging: bool = False,
) -> Dict[str, Any]:
    path = EG_SYNC / script
    if prefer_staging:
        staged = _staging_sync() / Path(script).name
        if staged.is_file():
            path = staged
    if not path.is_file():
        return {"ok": False, "error": f"missing {path}", "exit": 127}
    env = os.environ.copy()
    if extra_env:
        env.update(extra_env)
    env.setdefault("HOME", str(Path("/home") / TOPBOX_USER))
    cmd = _cmd_prefix() + ["/bin/bash", str(path)]
    started = time.monotonic()
    try:
        proc = subprocess.run(
            cmd,
            cwd=str(path.parent),
            capture_output=True,
            text=True,
            timeout=timeout,
            check=False,
            env=env,
        )
    except subprocess.TimeoutExpired as exc:
        return {
            "ok": False,
            "error": f"timeout after {timeout}s",
            "cmd": " ".join(cmd),
            "stdout": (exc.stdout or "")[-2000:] if isinstance(exc.stdout, str) else "",
            "stderr": (exc.stderr or "")[-2000:] if isinstance(exc.stderr, str) else "",
        }
    except OSError as exc:
        return {"ok": False, "error": str(exc), "cmd": " ".join(cmd)}
    return {
        "ok": proc.returncode == 0,
        "exit": proc.returncode,
        "seconds": round(time.monotonic() - started, 1),
        "stdout": (proc.stdout or "")[-4000:],
        "stderr": (proc.stderr or "")[-2000:],
        "cmd": " ".join(cmd),
    }


def _version(root: Path) -> str:
    return _read_text(root / "VERSION.txt") or _read_text(root / "prod" / "VERSION.txt")


def deploy_status() -> Dict[str, Any]:
    pending = EG_ROOT / "run" / "staging-pending"
    home = Path("/home") / TOPBOX_USER
    staging = Path(os.environ.get("ETHERGOLD_STAGING", str(home / "EtherGold-Staging")))
    rollback = Path(os.environ.get("ETHERGOLD_ROLLBACK", str(home / "EtherGold-Rollback")))
    rollback_empty = True
    if rollback.is_dir():
        try:
            rollback_empty = not any(rollback.iterdir())
        except OSError:
            rollback_empty = True
    hold = _read_text(EG_ROOT / "run" / "promote-hold")
    live_version = _version(EG_ROOT)
    staging_version = _version(staging)
    staging_older = _stamp_is_older(staging_version, live_version)
    pending_file = pending.is_file()
    return {
        "ok": True,
        "product": "ethergold",
        "eg_root": str(EG_ROOT),
        "live_version": live_version,
        "staging_version": staging_version,
        "rollback_version": _version(rollback),
        "staging_pending": pending_file and not staging_older,
        "staging_older": staging_older,
        "staging_pending_text": _read_text(pending) if pending_file else "",
        "promote_hold": bool(hold),
        "promote_hold_text": hold,
        "last_promote": _read_text(EG_ROOT / "run" / "last-promote.txt"),
        "last_rollback": _read_text(EG_ROOT / "run" / "last-rollback.txt"),
        "last_rollback_directive": _read_text(EG_ROOT / "run" / "last-rollback-directive"),
        "rollback_available": not rollback_empty,
        "rollback_dir": str(rollback),
        "staging_dir": str(staging),
    }


def check_for_updates() -> Dict[str, Any]:
    """rclone SGVI → Staging only. Does not promote or restart listen/HTTP."""
    stage = _run_eg_script(
        _first_script("stage_from_dropbox.sh", "stage-from-dropbox.sh"),
        timeout=STAGE_TIMEOUT_SEC,
    )
    status = deploy_status()
    pending = bool(status.get("staging_pending"))
    available = bool(stage.get("ok")) and pending
    return {
        "ok": bool(stage.get("ok")),
        "step": "check",
        "update_available": available,
        "stage": stage,
        "status": status,
    }


def update_now() -> Dict[str, Any]:
    stage = _run_eg_script(
        _first_script("stage_from_dropbox.sh", "stage-from-dropbox.sh"),
        timeout=STAGE_TIMEOUT_SEC,
    )
    if not stage.get("ok"):
        return {"ok": False, "step": "stage", "result": stage, "status": deploy_status()}
    promote = _run_eg_script(
        _first_script("promote_staging.sh", "promote-staging.sh"),
        extra_env={"FORCE": "1", "PROMOTE_RESTART": "1"},
        timeout=PROMOTE_TIMEOUT_SEC,
        prefer_staging=True,
    )
    return {
        "ok": bool(promote.get("ok")),
        "step": "promote" if promote.get("ok") else "promote_failed",
        "stage": stage,
        "promote": promote,
        "status": deploy_status(),
    }


def promote_now(confirm_hold: bool = False) -> Dict[str, Any]:
    env = {"FORCE": "1", "PROMOTE_RESTART": "1"}
    if confirm_hold:
        env["CONFIRM_PROMOTE_HOLD"] = "1"
    promote = _run_eg_script(
        _first_script("promote_staging.sh", "promote-staging.sh"),
        extra_env=env,
        timeout=PROMOTE_TIMEOUT_SEC,
        prefer_staging=True,
    )
    return {
        "ok": bool(promote.get("ok")),
        "step": "promote" if promote.get("ok") else "promote_failed",
        "promote": promote,
        "status": deploy_status(),
    }


def rollback_now() -> Dict[str, Any]:
    result = _run_eg_script(
        "rollback_live.sh",
        extra_env={"RESTART": "1"},
        timeout=PROMOTE_TIMEOUT_SEC,
    )
    return {
        "ok": bool(result.get("ok")),
        "step": "rollback",
        "result": result,
        "status": deploy_status(),
    }
