//! Elevated One-Shot-Setup: EIN UAC-Prompt erledigt alles —
//! DoH-Konfiguration, ProgramData-State, Tamper-Service-Installation.
//! Danach läuft alles silent (Service als SYSTEM, Deaktivierung server-gesteuert).

use protection_core::{apply_script, ProtectionState, SERVICE_NAME};
use std::net::ToSocketAddrs;
use std::path::PathBuf;

use protection_core::{ps_run, DOH_FALLBACK_IP, DOH_HOST};

/// Löst die IPv4 des DoH-Hosts auf (solange DNS noch normal funktioniert).
/// Fallback auf die bekannte Hetzner-IP wenn Resolve fehlschlägt.
pub fn resolve_doh_ip() -> String {
    if let Ok(addrs) = format!("{DOH_HOST}:443").to_socket_addrs() {
        for addr in addrs {
            if let std::net::SocketAddr::V4(v4) = addr {
                return v4.ip().to_string();
            }
        }
    }
    DOH_FALLBACK_IP.to_string()
}

/// Pfad des Tamper-Service-Binaries (Tauri-Sidecar, liegt neben der Haupt-Exe).
pub fn service_exe_path() -> Result<PathBuf, String> {
    let exe = std::env::current_exe().map_err(|e| e.to_string())?;
    let dir = exe
        .parent()
        .ok_or_else(|| "Exe-Verzeichnis nicht ermittelbar".to_string())?;
    Ok(dir.join("rebreak-protection-service.exe"))
}

/// Baut das komplette elevated Setup-Script:
/// 1. DoH anwenden  2. State nach %ProgramData%\ReBreak  3. ACL härten  4. Service installieren+starten
pub fn build_setup_script(state: &ProtectionState, service_exe: &str) -> String {
    let mut script = String::new();

    script.push_str(&apply_script(&state.server_ip, &state.doh_template));

    let state_dir = protection_core::state_dir();
    let state_path = protection_core::state_path();
    let json = serde_json::to_string_pretty(state).unwrap_or_default();

    script.push_str(&format!(
        r#"
# Persistenter State für den Tamper-Service (SYSTEM liest das beim Polling)
New-Item -ItemType Directory -Force -Path '{state_dir}' | Out-Null
@'
{json}
'@ | Set-Content -Path '{state_path}' -Encoding UTF8

# ACL härten: nur SYSTEM + Administratoren dürfen lesen/schreiben.
# Standard-User verlieren Read-Zugriff → DNS-Token nicht trivial auslesbar.
$acl = New-Object System.Security.AccessControl.FileSecurity
$acl.SetAccessRuleProtection($true, $false)  # Vererbung deaktivieren
$ruleSystem = New-Object System.Security.AccessControl.FileSystemAccessRule(
    "NT AUTHORITY\SYSTEM",
    "FullControl",
    "Allow"
)
$ruleAdmins = New-Object System.Security.AccessControl.FileSystemAccessRule(
    "BUILTIN\Administrators",
    "FullControl",
    "Allow"
)
$acl.AddAccessRule($ruleSystem)
$acl.AddAccessRule($ruleAdmins)
Set-Acl -Path '{state_path}' -AclObject $acl

# Tamper-Protection-Service als SYSTEM installieren (idempotent: alte Instanz weg)
sc.exe stop {SERVICE_NAME} 2>$null | Out-Null
sc.exe delete {SERVICE_NAME} 2>$null | Out-Null
sc.exe create {SERVICE_NAME} binPath= '"{service_exe}"' start= auto obj= LocalSystem DisplayName= "ReBreak Protection" | Out-Null
sc.exe description {SERVICE_NAME} "ReBreak DNS-Schutz — Tamper-Monitor (alle 5 Minuten)" | Out-Null
sc.exe failure {SERVICE_NAME} reset= 86400 actions= restart/60000/restart/60000/restart/60000 | Out-Null
sc.exe start {SERVICE_NAME} | Out-Null

exit 0
"#,
        state_dir = state_dir.display(),
        state_path = state_path.display(),
    ));

    script
}

/// Schreibt das Script als Temp-.ps1 und startet es elevated (UAC) mit -Wait.
/// Gibt Err zurück wenn der User den UAC-Prompt abbricht oder das Script failt.
pub fn run_elevated(script: &str) -> Result<(), String> {
    let tmp = std::env::temp_dir().join(format!(
        "rebreak-setup-{}.ps1",
        std::process::id()
    ));
    std::fs::write(&tmp, script).map_err(|e| format!("Temp-Script: {e}"))?;

    // Outer-PS startet Inner-PS elevated; -Wait + -PassThru für Exit-Code
    let outer = format!(
        r#"$p = Start-Process powershell.exe -Verb RunAs -Wait -PassThru -WindowStyle Hidden -ArgumentList @('-NonInteractive','-NoProfile','-ExecutionPolicy','Bypass','-File','{}')
exit $p.ExitCode"#,
        tmp.display()
    );

    let result = ps_run(&outer);
    let _ = std::fs::remove_file(&tmp);

    match result {
        Ok(_) => Ok(()),
        Err(e) if e.contains("canceled") || e.contains("abgebrochen") => {
            Err("Administrator-Bestätigung wurde abgebrochen.".into())
        }
        Err(e) => Err(format!("Setup fehlgeschlagen: {e}")),
    }
}
