use std::process::Command;
use std::path::PathBuf;
use tempfile::TempDir;

fn citrine_bin() -> PathBuf {
    PathBuf::from(env!("CARGO_BIN_EXE_citrine"))
}

fn run_citrine(dir: &std::path::Path, args: &[&str]) -> std::process::Output {
    Command::new(citrine_bin())
        .current_dir(dir)
        .args(args)
        .output()
        .expect("Failed to execute citrine")
}

fn stdout(output: &std::process::Output) -> String {
    String::from_utf8_lossy(&output.stdout).to_string()
}

fn stderr(output: &std::process::Output) -> String {
    String::from_utf8_lossy(&output.stderr).to_string()
}

#[test]
fn test_init() {
    let dir = TempDir::new().unwrap();
    let out = run_citrine(dir.path(), &["init"]);
    assert!(out.status.success());
    assert!(stdout(&out).contains("Initialized citrine"));
    assert!(dir.path().join(".citrine").exists());
    assert!(dir.path().join(".gitignore").exists());
    let gitignore = std::fs::read_to_string(dir.path().join(".gitignore")).unwrap();
    assert!(gitignore.contains(".citrine.local"));
}

#[test]
fn test_init_idempotent() {
    let dir = TempDir::new().unwrap();
    run_citrine(dir.path(), &["init"]);
    let out = run_citrine(dir.path(), &["init"]);
    assert!(out.status.success());
}

#[test]
fn test_add_and_list() {
    let dir = TempDir::new().unwrap();
    run_citrine(dir.path(), &["init"]);

    let out = run_citrine(dir.path(), &["add", "My first task"]);
    assert!(out.status.success());
    assert!(stdout(&out).contains("Added task"));
    assert!(stdout(&out).contains("My first task"));

    let out = run_citrine(dir.path(), &["list"]);
    assert!(out.status.success());
    assert!(stdout(&out).contains("My first task"));
}

#[test]
fn test_add_local() {
    let dir = TempDir::new().unwrap();
    run_citrine(dir.path(), &["init"]);

    let out = run_citrine(dir.path(), &["add", "Local task", "--local"]);
    assert!(out.status.success());
    assert!(stdout(&out).contains("(local)"));
    assert!(dir.path().join(".citrine.local").exists());

    // Should appear in list
    let out = run_citrine(dir.path(), &["list"]);
    assert!(stdout(&out).contains("Local task"));

    // Should appear in list --local
    let out = run_citrine(dir.path(), &["list", "--local"]);
    assert!(stdout(&out).contains("Local task"));
}

#[test]
fn test_add_not_initialized() {
    let dir = TempDir::new().unwrap();
    let out = run_citrine(dir.path(), &["add", "Some task"]);
    assert!(!out.status.success());
    assert_eq!(out.status.code(), Some(3));
    assert!(stderr(&out).contains("Not initialized"));
}

#[test]
fn test_set_status() {
    let dir = TempDir::new().unwrap();
    run_citrine(dir.path(), &["init"]);
    let out = run_citrine(dir.path(), &["add", "Test task"]);
    let hash = extract_hash(&stdout(&out));

    let out = run_citrine(dir.path(), &["set", &hash, "progress"]);
    assert!(out.status.success());
    assert!(stdout(&out).contains("→"));

    let out = run_citrine(dir.path(), &["list"]);
    assert!(stdout(&out).contains("[>]"));
}

#[test]
fn test_done_adds_completed() {
    let dir = TempDir::new().unwrap();
    run_citrine(dir.path(), &["init"]);
    let out = run_citrine(dir.path(), &["add", "Finish this"]);
    let hash = extract_hash(&stdout(&out));

    let out = run_citrine(dir.path(), &["done", &hash]);
    assert!(out.status.success());
    assert!(stdout(&out).contains("@completed:"));

    let content = std::fs::read_to_string(dir.path().join(".citrine")).unwrap();
    assert!(content.contains("[x]"));
    assert!(content.contains("@completed:"));
}

#[test]
fn test_rm() {
    let dir = TempDir::new().unwrap();
    run_citrine(dir.path(), &["init"]);
    let out = run_citrine(dir.path(), &["add", "Remove me"]);
    let hash = extract_hash(&stdout(&out));

    let out = run_citrine(dir.path(), &["rm", &hash]);
    assert!(out.status.success());
    assert!(stdout(&out).contains("Removed"));

    let out = run_citrine(dir.path(), &["list"]);
    assert!(!stdout(&out).contains("Remove me"));
}

#[test]
fn test_partial_hash() {
    let dir = TempDir::new().unwrap();
    run_citrine(dir.path(), &["init"]);
    let out = run_citrine(dir.path(), &["add", "Partial hash test"]);
    let hash = extract_hash(&stdout(&out));
    let partial = &hash[..2];

    let out = run_citrine(dir.path(), &["set", partial, "progress"]);
    assert!(out.status.success());
}

#[test]
fn test_invalid_status() {
    let dir = TempDir::new().unwrap();
    run_citrine(dir.path(), &["init"]);
    let out = run_citrine(dir.path(), &["add", "Task"]);
    let hash = extract_hash(&stdout(&out));

    let out = run_citrine(dir.path(), &["set", &hash, "invalid"]);
    assert!(!out.status.success());
    assert_eq!(out.status.code(), Some(2));
}

#[test]
fn test_hash_not_found() {
    let dir = TempDir::new().unwrap();
    run_citrine(dir.path(), &["init"]);
    let out = run_citrine(dir.path(), &["set", "ffff", "done"]);
    assert!(!out.status.success());
    assert!(stderr(&out).contains("Task not found"));
}

#[test]
fn test_status_filter() {
    let dir = TempDir::new().unwrap();
    run_citrine(dir.path(), &["init"]);

    run_citrine(dir.path(), &["add", "Todo task"]);
    let out = run_citrine(dir.path(), &["add", "Done task"]);
    let hash = extract_hash(&stdout(&out));
    run_citrine(dir.path(), &["done", &hash]);

    let out = run_citrine(dir.path(), &["list", "--status", "todo"]);
    let s = stdout(&out);
    assert!(s.contains("Todo task"));
    assert!(!s.contains("Done task"));

    let out = run_citrine(dir.path(), &["list", "--status", "done"]);
    let s = stdout(&out);
    assert!(!s.contains("Todo task"));
    assert!(s.contains("Done task"));
}

#[test]
fn test_merge_main_and_local() {
    let dir = TempDir::new().unwrap();
    run_citrine(dir.path(), &["init"]);
    run_citrine(dir.path(), &["add", "Main task"]);
    run_citrine(dir.path(), &["add", "Local task", "--local"]);

    let out = run_citrine(dir.path(), &["list"]);
    let s = stdout(&out);
    assert!(s.contains("Main task"));
    assert!(s.contains("Local task"));
}

fn extract_hash(output: &str) -> String {
    let re = regex::Regex::new(r"\[([0-9a-f]{4})\]").unwrap();
    re.captures(output).unwrap()[1].to_string()
}
