//! ReBreak Tamper-Protection-Service (Windows-Service, läuft als SYSTEM).
//!
//! Alle 5 Minuten:
//!   1. Backend-Poll: GET /api/magic/status?token=<dnsToken>
//!      - active=true  → DoH-Config prüfen, bei Manipulation sofort re-applien
//!      - active=false → 24h-Release-Cooldown ist durch: Teardown + Service disablen
//!      - offline      → FAIL-CLOSED: Schutz bleibt, Manipulation wird trotzdem repariert
//!
//! Deaktivierung ist damit ausschließlich server-zeitgesteuert — die App
//! (unelevated) kann den Schutz nicht abbauen, der User sieht keinen Code.
//!
//! Debug: `rebreak-protection-service.exe --console` läuft im Vordergrund.

use protection_core::{
    apply_doh, doh_is_applied, load_state, state_dir, teardown_doh, ProtectionState,
    SERVICE_NAME,
};
use std::time::Duration;

const CHECK_INTERVAL: Duration = Duration::from_secs(5 * 60);
const STARTUP_DELAY: Duration = Duration::from_secs(20);
const HTTP_TIMEOUT: Duration = Duration::from_secs(15);

fn log(msg: &str) {
    let line = format!(
        "[{}] {msg}\n",
        std::time::SystemTime::now()
            .duration_since(std::time::SystemTime::UNIX_EPOCH)
            .map(|d| d.as_secs())
            .unwrap_or(0)
    );
    let path = state_dir().join("service.log");
    if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open(path) {
        use std::io::Write;
        let _ = f.write_all(line.as_bytes());
    }
}

/// Backend-Status: Some(true)=aktiv, Some(false)=revoked, None=offline/Fehler.
fn poll_backend(state: &ProtectionState) -> Option<bool> {
    let client = reqwest::blocking::Client::builder()
        .timeout(HTTP_TIMEOUT)
        .build()
        .ok()?;
    let url = format!(
        "{}/api/magic/status?token={}",
        state.backend_url.trim_end_matches('/'),
        state.dns_token
    );
    let res = client.get(&url).send().ok()?;
    if !res.status().is_success() {
        // 4xx/5xx ≠ "revoked" — nur explizites active=false zählt
        return None;
    }
    let body: serde_json::Value = res.json().ok()?;
    body.pointer("/data/active").and_then(|v| v.as_bool())
}

/// Ein Monitor-Durchlauf. Rückgabe true = Service soll sich beenden (released).
fn tick() -> bool {
    let Some(state) = load_state() else {
        log("Kein protection.json — idle.");
        return false;
    };

    match poll_backend(&state) {
        Some(false) => {
            // Release-Cooldown abgelaufen, Token serverseitig revoked → Abbau
            log("Backend: Token revoked → Teardown.");
            match teardown_doh(&state.server_ip) {
                Ok(()) => {
                    let _ = std::fs::remove_file(protection_core::state_path());
                    // Autostart aus — Binary bleibt liegen bis App-Uninstall
                    let _ = protection_core::ps_run(&format!(
                        "sc.exe config {SERVICE_NAME} start= disabled"
                    ));
                    log("Teardown ok — Service deaktiviert sich.");
                    true
                }
                Err(e) => {
                    log(&format!("Teardown fehlgeschlagen: {e}"));
                    false
                }
            }
        }
        status => {
            // active=true ODER offline → fail-closed: Schutz sicherstellen
            if status.is_none() {
                log("Backend nicht erreichbar — fail-closed, Schutz bleibt.");
            }
            if !doh_is_applied(&state.server_ip) {
                log("Manipulation erkannt — DoH wird wiederhergestellt.");
                match apply_doh(&state.server_ip, &state.doh_template) {
                    Ok(()) => log("DoH wiederhergestellt."),
                    Err(e) => log(&format!("Re-Apply fehlgeschlagen: {e}")),
                }
            }
            false
        }
    }
}

/// Monitor-Loop; `stop_check` liefert true sobald der SCM Stop signalisiert.
fn monitor_loop(stop_check: impl Fn(Duration) -> bool) {
    log("Service gestartet.");
    if stop_check(STARTUP_DELAY) {
        return;
    }
    loop {
        let done = tick();
        if done || stop_check(CHECK_INTERVAL) {
            break;
        }
    }
    log("Service beendet.");
}

// ---------------------------------------------------------------------------
// Windows-Service-Boilerplate
// ---------------------------------------------------------------------------

#[cfg(windows)]
mod win {
    use super::*;
    use std::ffi::OsString;
    use std::sync::mpsc;
    use windows_service::{
        define_windows_service,
        service::{
            ServiceControl, ServiceControlAccept, ServiceExitCode, ServiceState,
            ServiceStatus, ServiceType,
        },
        service_control_handler::{self, ServiceControlHandlerResult},
        service_dispatcher,
    };

    define_windows_service!(ffi_service_main, service_main);

    pub fn run() -> Result<(), windows_service::Error> {
        service_dispatcher::start(SERVICE_NAME, ffi_service_main)
    }

    fn service_main(_args: Vec<OsString>) {
        if let Err(e) = run_service() {
            log(&format!("Service-Fehler: {e:?}"));
        }
    }

    fn run_service() -> Result<(), windows_service::Error> {
        let (stop_tx, stop_rx) = mpsc::channel::<()>();

        let handler = move |control| match control {
            ServiceControl::Stop | ServiceControl::Shutdown => {
                let _ = stop_tx.send(());
                ServiceControlHandlerResult::NoError
            }
            ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
            _ => ServiceControlHandlerResult::NotImplemented,
        };
        let status = service_control_handler::register(SERVICE_NAME, handler)?;

        let running = |state: ServiceState| ServiceStatus {
            service_type: ServiceType::OWN_PROCESS,
            current_state: state,
            controls_accepted: ServiceControlAccept::STOP,
            exit_code: ServiceExitCode::Win32(0),
            checkpoint: 0,
            wait_hint: Duration::default(),
            process_id: None,
        };
        status.set_service_status(running(ServiceState::Running))?;

        monitor_loop(|wait| stop_rx.recv_timeout(wait).is_ok());

        status.set_service_status(running(ServiceState::Stopped))?;
        Ok(())
    }
}

fn main() {
    // --console: Vordergrund-Modus für Debugging (auch unter macOS-Dev lauffähig)
    if std::env::args().any(|a| a == "--console") {
        monitor_loop(|wait| {
            std::thread::sleep(wait);
            false
        });
        return;
    }

    #[cfg(windows)]
    {
        if let Err(e) = win::run() {
            log(&format!("Dispatcher-Fehler: {e:?}"));
        }
    }
    #[cfg(not(windows))]
    {
        eprintln!("rebreak-protection-service läuft nur als Windows-Service. Nutze --console für Debug.");
        std::process::exit(1);
    }
}
