#!/usr/bin/env python3
"""Patch /home/guru/gururmm/agent/src/updater/mod.rs with two fixes:
1. Replace restart_service() Windows block with detached-cmd approach.
2. Fix hardcoded "gururmm-agent" service name in create_windows_rollback_watchdog.
"""

import sys

PATH = "/home/guru/gururmm/agent/src/updater/mod.rs"

with open(PATH, "r") as f:
    src = f.read()

original = src

# ---------------------------------------------------------------------------
# Fix 1: restart_service()
# ---------------------------------------------------------------------------

OLD_RESTART = """\
    /// Restart the agent service
    async fn restart_service(&self) -> Result<()> {
        #[cfg(unix)]
        {
            info!("Exiting for service restart by systemd");
            std::process::exit(0);
        }

        #[cfg(windows)]
        {
            use crate::service::windows::SERVICE_NAME;
            // Restart Windows service
            tokio::process::Command::new("sc.exe")
                .args(["stop", SERVICE_NAME])
                .status()
                .await?;

            tokio::time::sleep(std::time::Duration::from_secs(2)).await;

            tokio::process::Command::new("sc.exe")
                .args(["start", SERVICE_NAME])
                .status()
                .await?;
        }

        // Give the new process a moment to start
        tokio::time::sleep(std::time::Duration::from_secs(1)).await;

        // Exit this process - the new version should be running now
        std::process::exit(0);
    }"""

NEW_RESTART = """\
    /// Restart the agent service
    async fn restart_service(&self) -> Result<()> {
        #[cfg(unix)]
        {
            info!("Exiting for service restart by systemd");
            std::process::exit(0);
        }

        #[cfg(windows)]
        {
            use crate::service::windows::SERVICE_NAME;

            // Spawn a detached cmd.exe that waits for this process to exit,
            // then starts the service. We cannot await sc start here because
            // calling sc stop on our own service causes the SCM to send
            // SERVICE_CONTROL_STOP back to this process, which tears down the
            // tokio runtime and cancels any pending awaits before sc start runs.
            let restart_cmd = format!(
                "timeout /t 5 /nobreak >nul 2>&1 & sc.exe start {}",
                SERVICE_NAME
            );
            match std::process::Command::new("cmd")
                .args(["/c", &restart_cmd])
                .creation_flags(0x08000000) // CREATE_NO_WINDOW
                .spawn()
            {
                Ok(_) => info!("Restart helper spawned, exiting for service stop/start"),
                Err(e) => error!("Failed to spawn restart helper: {} — service will stay stopped", e),
            }

            // Exit this process. SCM detects the exit and marks the service
            // Stopped. The detached cmd above will sc start after 5 seconds.
            std::process::exit(0);
        }

        // Unreachable on Windows (process::exit above), but needed for
        // Unix path and to satisfy the return type.
        #[allow(unreachable_code)]
        Ok(())
    }"""

if OLD_RESTART not in src:
    print("ERROR: restart_service old block not found in file", file=sys.stderr)
    idx = src.find("async fn restart_service")
    if idx != -1:
        print(f"restart_service found at offset {idx}:", file=sys.stderr)
        print(repr(src[idx:idx+1000]), file=sys.stderr)
    sys.exit(1)

src = src.replace(OLD_RESTART, NEW_RESTART, 1)
print("[OK] Fix 1 applied: restart_service() replaced with detached-cmd approach")

# ---------------------------------------------------------------------------
# Fix 2: create_windows_rollback_watchdog
# ---------------------------------------------------------------------------

# 2a: Replace three hardcoded service name strings in the PS script template
replacements = [
    ('$service = Get-Service -Name "gururmm-agent" -ErrorAction SilentlyContinue',
     '$service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue',
     'Get-Service line'),
    ('Stop-Service -Name "gururmm-agent" -Force -ErrorAction SilentlyContinue',
     'Stop-Service -Name $ServiceName -Force -ErrorAction SilentlyContinue',
     'Stop-Service line'),
    ('Start-Service -Name "gururmm-agent"',
     'Start-Service -Name $ServiceName',
     'Start-Service line'),
]

for old, new, label in replacements:
    if old not in src:
        print(f"ERROR: {label} not found", file=sys.stderr)
        sys.exit(1)
    src = src.replace(old, new, 1)
    print(f"[OK] Fix 2a: replaced {label}")

# 2b: Add $ServiceName PS variable after $Timeout assignment in the script template
OLD_TIMEOUT_LINE = '$Timeout = {timeout}\n\nStart-Sleep -Seconds $Timeout'
NEW_TIMEOUT_LINE = '$Timeout = {timeout}\n$ServiceName = "{service_name}"\n\nStart-Sleep -Seconds $Timeout'

if OLD_TIMEOUT_LINE not in src:
    print("ERROR: $Timeout line context not found", file=sys.stderr)
    sys.exit(1)
src = src.replace(OLD_TIMEOUT_LINE, NEW_TIMEOUT_LINE, 1)
print("[OK] Fix 2b: $ServiceName PS variable added to script template")

# 2c: Add service_name = SERVICE_NAME to format! args
# The actual bytes in the file for the format args closing (confirmed via repr):
#   replace('\', "\\"),\n            timeout = timeout\n        );
# In Python string literals (raw bytes via repr):
#   replace(\'\\\\', "\\\\\\\\"),\n            timeout = timeout\n        );
OLD_FORMAT_TAIL = "            timeout = timeout\n        );"
NEW_FORMAT_TAIL = "            timeout = timeout,\n            service_name = SERVICE_NAME\n        );"

# There are two format! calls in this file (Unix watchdog and Windows watchdog).
# The Unix one ends with: script = script_path.display()\n        );
# The Windows one ends with: timeout = timeout\n        );
# So OLD_FORMAT_TAIL is unique to the Windows block. Verify:
count = src.count(OLD_FORMAT_TAIL)
if count != 1:
    print(f"ERROR: expected 1 match for format tail, got {count}", file=sys.stderr)
    sys.exit(1)
src = src.replace(OLD_FORMAT_TAIL, NEW_FORMAT_TAIL, 1)
print("[OK] Fix 2c: service_name added to format! args")

# 2d: Add SERVICE_NAME import at top of the Windows watchdog function body
# The function opens with these lines (no prior use statement):
OLD_WATCHDOG_OPEN = (
    "    async fn create_windows_rollback_watchdog(&self) -> Result<()> {\n"
    "        let backup_path = self.config.backup_path();\n"
)
NEW_WATCHDOG_OPEN = (
    "    async fn create_windows_rollback_watchdog(&self) -> Result<()> {\n"
    "        use crate::service::windows::SERVICE_NAME;\n"
    "        let backup_path = self.config.backup_path();\n"
)

if OLD_WATCHDOG_OPEN not in src:
    print("ERROR: watchdog function opening not found", file=sys.stderr)
    sys.exit(1)
src = src.replace(OLD_WATCHDOG_OPEN, NEW_WATCHDOG_OPEN, 1)
print("[OK] Fix 2d: SERVICE_NAME import added to watchdog function")

# ---------------------------------------------------------------------------
# Write result
# ---------------------------------------------------------------------------
with open(PATH, "w") as f:
    f.write(src)

lines_changed = sum(1 for a, b in zip(original.splitlines(), src.splitlines()) if a != b)
lines_added = len(src.splitlines()) - len(original.splitlines())
print(f"\nFile written. ~{lines_changed} lines changed, {lines_added:+d} net lines.")
