use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::{Duration, Instant};

use rpki::storage::{RawByHashEntry, RepositoryViewEntry, RepositoryViewState, RocksStore};
use sha2::Digest;

const RSYNC_BASE_URI: &str = "rsync://rpki.luys.cloud/repo/LY-RPKI/1/";

fn run_rsync(src: &str, dst: &Path, timeout: Duration) -> Result<(), String> {
    let timeout_secs = timeout.as_secs().max(1).to_string();
    let out = Command::new("rsync")
        .arg("-rt")
        .arg("--delete")
        .arg("--timeout")
        .arg(timeout_secs)
        .arg(src)
        .arg(dst)
        .output()
        .map_err(|e| format!("rsync spawn failed: {e}"))?;
    if !out.status.success() {
        return Err(format!(
            "rsync failed: status={} stdout={} stderr={}",
            out.status,
            String::from_utf8_lossy(&out.stdout).trim(),
            String::from_utf8_lossy(&out.stderr).trim()
        ));
    }
    Ok(())
}

fn normalize_rsync_base_uri(s: &str) -> String {
    if s.ends_with('/') {
        s.to_string()
    } else {
        format!("{s}/")
    }
}

fn walk_files(root: &Path, current: &Path, out: &mut Vec<PathBuf>) -> Result<(), String> {
    for entry in std::fs::read_dir(current).map_err(|e| e.to_string())? {
        let entry = entry.map_err(|e| e.to_string())?;
        let path = entry.path();
        let meta = entry.metadata().map_err(|e| e.to_string())?;
        if meta.is_dir() {
            walk_files(root, &path, out)?;
        } else if meta.is_file() {
            out.push(path);
        }
    }
    Ok(())
}

#[test]
#[ignore = "live network rsync breakdown; prints timing table"]
fn rsync_fallback_breakdown_luys_cloud() {
    let rsync_base_uri = normalize_rsync_base_uri(RSYNC_BASE_URI);
    let timeout = Duration::from_secs(30 * 60);

    let temp_dir = tempfile::tempdir().expect("tempdir");
    let dst = temp_dir.path().join("rsync");
    std::fs::create_dir_all(&dst).expect("create dst dir");

    let t0 = Instant::now();
    run_rsync(&rsync_base_uri, &dst, timeout).expect("rsync fetch");
    let rsync_wall = t0.elapsed();

    let t1 = Instant::now();
    let mut files: Vec<PathBuf> = Vec::new();
    walk_files(&dst, &dst, &mut files).expect("walk files");
    let walk_wall = t1.elapsed();

    let t2 = Instant::now();
    let mut objects: Vec<(String, Vec<u8>)> = Vec::with_capacity(files.len());
    let mut total_bytes: u64 = 0;
    for path in &files {
        let rel = path
            .strip_prefix(&dst)
            .expect("strip prefix")
            .to_string_lossy()
            .replace('\\', "/");
        let uri = format!("{rsync_base_uri}{rel}");
        let bytes = std::fs::read(path).expect("read file");
        total_bytes = total_bytes.saturating_add(bytes.len() as u64);
        objects.push((uri, bytes));
    }
    let read_wall = t2.elapsed();

    let db_dir = tempfile::tempdir().expect("db tempdir");
    let store = RocksStore::open(db_dir.path()).expect("open rocksdb");

    let t3 = Instant::now();
    for (uri, bytes) in &objects {
        let sha256_hex = hex::encode(sha2::Sha256::digest(bytes));
        let mut raw = RawByHashEntry::from_bytes(sha256_hex.clone(), bytes.clone());
        raw.origin_uris.push(uri.clone());
        raw.object_type = Some(uri.rsplit('.').next().unwrap_or("bin").to_ascii_lowercase());
        raw.encoding = Some("der".to_string());
        store.put_raw_by_hash_entry(&raw).expect("put raw_by_hash");
        store
            .put_repository_view_entry(&RepositoryViewEntry {
                rsync_uri: uri.clone(),
                current_hash: Some(sha256_hex),
                repository_source: Some(rsync_base_uri.clone()),
                object_type: Some(uri.rsplit('.').next().unwrap_or("bin").to_ascii_lowercase()),
                state: RepositoryViewState::Present,
            })
            .expect("put repository view");
    }
    let write_wall = t3.elapsed();

    println!("rsync_base_uri: {rsync_base_uri}");
    println!("| phase | duration(ms) | pct | notes |\n|---|---:|---:|---|");
    let total = rsync_wall + walk_wall + read_wall + write_wall;
    let pct = |d: Duration| (d.as_secs_f64() / total.as_secs_f64()) * 100.0;
    println!(
        "| rsync_network | {:>11.3} | {:>5.1}% | rsync command wall time |",
        rsync_wall.as_secs_f64() * 1000.0,
        pct(rsync_wall)
    );
    println!(
        "| walk_files | {:>11.3} | {:>5.1}% | enumerate local files |",
        walk_wall.as_secs_f64() * 1000.0,
        pct(walk_wall)
    );
    println!(
        "| read_files | {:>11.3} | {:>5.1}% | read all bytes into memory |",
        read_wall.as_secs_f64() * 1000.0,
        pct(read_wall)
    );
    println!(
        "| rocksdb_put_current | {:>11.3} | {:>5.1}% | write repository_view + raw_by_hash ({} keys) |",
        write_wall.as_secs_f64() * 1000.0,
        pct(write_wall),
        objects.len()
    );
    println!(
        "| TOTAL | {:>11.3} | 100.0% | files={} bytes={} |",
        total.as_secs_f64() * 1000.0,
        objects.len(),
        total_bytes
    );
}
