from __future__ import annotations

import hashlib
import shlex
import subprocess
import sys
from pathlib import Path

from one_run.errors import OneRunError
from one_run.manifest import load_manifest
from one_run.utils.shell import command_exists


def _cron_marker(manifest_path: Path) -> str:
    digest = hashlib.sha1(str(manifest_path).encode("utf-8")).hexdigest()[:12]
    return f"one-run:{digest}"


def _validate_cron(expr: str) -> None:
    value = expr.strip()
    if not value:
        raise OneRunError("schedule.cron cannot be empty")
    if value.startswith("@"):
        return
    parts = value.split()
    if len(parts) != 5:
        raise OneRunError("schedule.cron must have 5 fields or start with @ (example: @daily)")


def _read_crontab() -> str:
    proc = subprocess.run(["crontab", "-l"], check=False, capture_output=True, text=True)
    if proc.returncode == 0:
        return proc.stdout
    stderr = (proc.stderr or "").lower()
    if "no crontab" in stderr:
        return ""
    raise OneRunError(f"failed to read crontab: {(proc.stderr or proc.stdout).strip()}")


def _write_crontab(text: str) -> None:
    proc = subprocess.run(["crontab", "-"], input=text, check=False, capture_output=True, text=True)
    if proc.returncode != 0:
        raise OneRunError(f"failed to write crontab: {(proc.stderr or proc.stdout).strip()}")


def _remove_marker_lines(text: str, marker: str) -> tuple[str, bool]:
    lines = text.splitlines()
    kept = [line for line in lines if marker not in line]
    removed = len(kept) != len(lines)
    out = "\n".join(kept).strip()
    if out:
        out += "\n"
    return out, removed


def _build_cron_line(*, cron_expr: str, project_root: Path, manifest_path: Path, marker: str, backend_kind: str) -> str:
    action = "submit" if backend_kind == "slurm" else "run"
    python_bin = shlex.quote(sys.executable)
    root = shlex.quote(str(project_root))
    manifest = shlex.quote(str(manifest_path))
    log_path = shlex.quote(str(project_root / "runs" / "cron.log"))
    return f"{cron_expr} cd {root} && {python_bin} -m one_run {action} {manifest} >> {log_path} 2>&1 # {marker}"


def install_cron(config_path: str) -> int:
    if not command_exists("crontab"):
        raise OneRunError("crontab command not found on this system")

    manifest_path = Path(config_path).resolve()
    manifest = load_manifest(manifest_path)
    schedule = manifest.schedule
    if schedule is None:
        raise OneRunError("manifest has no schedule section")
    if not schedule.enabled:
        raise OneRunError("schedule.enabled is false")
    _validate_cron(schedule.cron)

    marker = _cron_marker(manifest_path)
    project_root = Path.cwd().resolve()
    line = _build_cron_line(
        cron_expr=schedule.cron.strip(),
        project_root=project_root,
        manifest_path=manifest_path,
        marker=marker,
        backend_kind=manifest.backend.kind,
    )

    current = _read_crontab()
    cleaned, _ = _remove_marker_lines(current, marker)
    new_text = (cleaned + line + "\n")
    _write_crontab(new_text)

    print(f"installed=true")
    print(f"marker={marker}")
    print(f"manifest={manifest_path}")
    print(f"cron={schedule.cron.strip()}")
    return 0


def remove_cron(config_path: str) -> int:
    if not command_exists("crontab"):
        raise OneRunError("crontab command not found on this system")
    manifest_path = Path(config_path).resolve()
    marker = _cron_marker(manifest_path)
    current = _read_crontab()
    new_text, removed = _remove_marker_lines(current, marker)
    if removed:
        _write_crontab(new_text)
    print(f"removed={'true' if removed else 'false'}")
    print(f"marker={marker}")
    print(f"manifest={manifest_path}")
    return 0


def show_cron(config_path: str) -> int:
    if not command_exists("crontab"):
        raise OneRunError("crontab command not found on this system")
    manifest_path = Path(config_path).resolve()
    marker = _cron_marker(manifest_path)
    current = _read_crontab()
    lines = [line for line in current.splitlines() if marker in line]
    if not lines:
        print("installed=false")
        print(f"marker={marker}")
        print(f"manifest={manifest_path}")
        return 0
    print("installed=true")
    print(f"marker={marker}")
    print(f"manifest={manifest_path}")
    print(f"entry={lines[0]}")
    return 0
