use std::{
    env, fs,
    path::{Path, PathBuf},
    process::{Command, Output},
    time::{SystemTime, UNIX_EPOCH},
};

use serde_json::Value;

fn bin() -> Command {
    Command::new(env!("CARGO_BIN_EXE_dioprism"))
}

fn success(mut command: Command) -> Output {
    let output = command.output().expect("command runs");
    assert!(
        output.status.success(),
        "command failed\nstdout:\n{}\nstderr:\n{}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    );
    output
}

fn stdout_json(output: &Output) -> Value {
    serde_json::from_slice(&output.stdout).expect("stdout is JSON")
}

struct TestDir {
    path: PathBuf,
}

impl TestDir {
    fn new() -> Self {
        let mut path = env::temp_dir();
        let nanos = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("system time is after epoch")
            .as_nanos();
        path.push(format!("dioprism-cli-test-{}-{nanos}", std::process::id()));
        fs::create_dir_all(&path).expect("temp dir created");
        Self { path }
    }

    fn join(&self, file: impl AsRef<Path>) -> PathBuf {
        self.path.join(file)
    }
}

impl Drop for TestDir {
    fn drop(&mut self) {
        let _ = fs::remove_dir_all(&self.path);
    }
}

#[test]
fn manifest_writes_output_and_stdout_json() {
    let temp = TestDir::new();
    let manifest_path = temp.join("manifest.json");

    let output = success({
        let mut command = bin();
        command
            .arg("manifest")
            .arg("--route")
            .arg("/dioprism-test")
            .arg("--output")
            .arg(&manifest_path);
        command
    });

    let stdout = stdout_json(&output);
    let written: Value =
        serde_json::from_str(&fs::read_to_string(manifest_path).expect("manifest was written"))
            .expect("written manifest is JSON");

    assert_eq!(stdout["routes"][0]["path"], "/dioprism-test");
    assert_eq!(written["routes"][0]["path"], "/dioprism-test");
    assert_eq!(stdout["optimizationPlan"]["version"], "2");
    assert!(
        stdout["optimizationPlan"]["routeGraph"]["edges"]
            .as_array()
            .expect("edges array")
            .len()
            >= 6
    );
}

#[test]
fn file_backed_reports_emit_json_and_optional_files() {
    let temp = TestDir::new();
    let manifest_path = temp.join("manifest.json");
    let audit_html = temp.join("audit.html");
    let marks_path = temp.join("marks.txt");

    success({
        let mut command = bin();
        command
            .arg("manifest")
            .arg("--route")
            .arg("/from-file")
            .arg("--output")
            .arg(&manifest_path);
        command
    });
    fs::write(
        &marks_path,
        "dioprism:boot:start\ndioprism:island:visible:test\n",
    )
    .expect("marks file written");

    let analyze = success({
        let mut command = bin();
        command.arg("analyze").arg("--manifest").arg(&manifest_path);
        command
    });
    let analyze = stdout_json(&analyze);
    assert_eq!(analyze["route"], "/from-file");
    assert!(analyze["routeCount"].as_u64().expect("route count") >= 7);

    let audit = success({
        let mut command = bin();
        command
            .arg("audit")
            .arg("--manifest")
            .arg(&manifest_path)
            .arg("--html")
            .arg(&audit_html);
        command
    });
    let audit = stdout_json(&audit);
    assert_eq!(audit["route"], "/from-file");
    assert!(matches!(audit["status"].as_str(), Some("pass" | "warn")));
    assert!(
        fs::read_to_string(audit_html)
            .expect("audit HTML was written")
            .contains("Dioxus Dioprism Audit")
    );

    let profile = success({
        let mut command = bin();
        command
            .arg("profile")
            .arg("--manifest")
            .arg(&manifest_path)
            .arg("--marks")
            .arg(&marks_path);
        command
    });
    let profile = stdout_json(&profile);
    assert_eq!(profile["route"], "/from-file");
    assert_eq!(profile["providedMarks"][0], "dioprism:boot:start");
    assert_eq!(profile["optimizationPlanVersion"], "2");
    assert_eq!(
        profile["telemetryProfile"]["observedMarks"][0],
        "dioprism:boot:start"
    );

    let plan_output = success({
        let mut command = bin();
        command
            .arg("plan")
            .arg("--manifest")
            .arg(&manifest_path)
            .arg("--route")
            .arg("/from-file");
        command
    });
    let plan = stdout_json(&plan_output);
    assert_eq!(plan["version"], "2");
    assert_eq!(plan["entryRoute"], "/from-file");
    assert!(
        !plan["assetDecisions"]
            .as_array()
            .expect("asset decisions")
            .is_empty()
    );
}

#[test]
fn compare_json_and_legacy_positional_route_work() {
    let temp = TestDir::new();
    let baseline_path = temp.join("baseline.json");
    let dioprism_path = temp.join("dioprism.json");

    success({
        let mut command = bin();
        command
            .arg("manifest")
            .arg("--route")
            .arg("/baseline")
            .arg("--output")
            .arg(&baseline_path);
        command
    });
    success({
        let mut command = bin();
        command
            .arg("manifest")
            .arg("--route")
            .arg("/dioprism")
            .arg("--output")
            .arg(&dioprism_path);
        command
    });

    let compare = success({
        let mut command = bin();
        command
            .arg("compare")
            .arg("--baseline")
            .arg(&baseline_path)
            .arg("--dioprism")
            .arg(&dioprism_path)
            .arg("--json");
        command
    });
    let compare = stdout_json(&compare);
    assert_eq!(compare["baseline"]["route"], "/baseline");
    assert_eq!(compare["dioprism"]["route"], "/dioprism");
    assert_eq!(compare["delta"]["routes"], 0);
    assert!(
        compare["dioprism"]["routeReadyForInstantNavigation"]
            .as_bool()
            .is_some()
    );

    let legacy_audit = success({
        let mut command = bin();
        command.arg("audit").arg("/legacy-route");
        command
    });
    let legacy_audit = stdout_json(&legacy_audit);
    assert_eq!(legacy_audit["route"], "/legacy-route");

    let legacy_compare = success({
        let mut command = bin();
        command.arg("compare").arg("/legacy-route");
        command
    });
    let table = String::from_utf8_lossy(&legacy_compare.stdout);
    assert!(table.contains("Metric"));
    assert!(table.contains("Islands"));
}

#[test]
fn library_integration_adapter_explains_and_batches_manifest_records() {
    let adapter = dioprism_cli::DioprismCliCommandAdapter::new(
        "plan",
        ["--route", "/dioprism", "--manifest", "dioprism.json"],
    )
    .label("owner", "platform");
    let manifest = dioprism_ssr::dioprism_demo_manifest("/dioprism");
    let payload = dioprism_cli::dioprism_cli_batch_payload(&manifest);
    let config = dioprism_cli::dioprism_cli_config().route_enabled("/dioprism", true);
    let diagnostic = adapter.explain();

    assert!(config.is_enabled_for_route("/dioprism"));
    assert_eq!(diagnostic.code, "dioprism-cli-adapter");
    assert_eq!(diagnostic.subject.as_deref(), Some("plan"));
    assert!(!adapter.cache_key.is_empty());
    assert!(!payload.records.is_empty());
    assert_eq!(
        dioprism_cli::DIOPRISM_CLI_INTEGRATION_RANK_COVERAGE.len(),
        50
    );
}
