use assert_cmd::cargo::cargo_bin_cmd;
use predicates::prelude::*;
use std::fs;
use std::path::PathBuf;
use tempfile::TempDir;

fn create_git_repo(dir: &PathBuf) -> std::process::Output {
    std::process::Command::new("git")
        .args(&["init"])
        .current_dir(dir)
        .output()
        .expect("Failed to init git repo")
}

fn configure_git_user(dir: &PathBuf) {
    std::process::Command::new("git")
        .args(&["config", "user.name", "Test User"])
        .current_dir(dir)
        .output()
        .expect("Failed to configure git user name");

    std::process::Command::new("git")
        .args(&["config", "user.email", "test@example.com"])
        .current_dir(dir)
        .output()
        .expect("Failed to configure git user email");
}

fn create_test_file(dir: &PathBuf, name: &str, content: &str) {
    let file_path = dir.join(name);
    fs::write(&file_path, content).expect("Failed to create test file");
}

fn stage_file(dir: &PathBuf, name: &str) {
    std::process::Command::new("git")
        .args(&["add", name])
        .current_dir(dir)
        .output()
        .expect("Failed to stage file");
}

fn create_commit(dir: &PathBuf, message: &str) {
    std::process::Command::new("git")
        .args(&["commit", "-m", message])
        .current_dir(dir)
        .output()
        .expect("Failed to create commit");
}

fn setup_git_repo(dir: &PathBuf) {
    create_git_repo(dir);
    configure_git_user(dir);
}

fn setup_test_repo_with_file(dir: &PathBuf, file_name: &str, file_content: &str) {
    setup_git_repo(dir);
    create_test_file(dir, file_name, file_content);
    stage_file(dir, file_name);
}

fn init_quicommit(dir: &PathBuf, config_path: &PathBuf) {
    let mut cmd = cargo_bin_cmd!("quicommit");
    cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()])
        .current_dir(dir);
    cmd.assert().success();
}

mod cli_basic {
    use super::*;

    #[test]
    fn test_help() {
        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.arg("--help");
        cmd.assert()
            .success()
            .stdout(predicate::str::contains("QuiCommit"))
            .stdout(predicate::str::contains("AI-powered Git assistant"))
            .stdout(predicate::str::contains("Usage:"))
            .stdout(predicate::str::contains("Commands:"))
            .stdout(predicate::str::contains("Options:"));
    }

    #[test]
    fn test_version() {
        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.arg("--version");
        cmd.assert()
            .success()
            .stdout(predicate::str::contains("quicommit"));
    }

    #[test]
    fn test_no_args_shows_help() {
        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.assert()
            .failure()
            .stderr(predicate::str::contains("Usage:"));
    }

    #[test]
    fn test_verbose_flag() {
        let temp_dir = TempDir::new().unwrap();
        let repo_path = temp_dir.path().to_path_buf();
        let config_path = repo_path.join("config.toml");
        create_git_repo(&repo_path);
        configure_git_user(&repo_path);

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&[
            "-vv",
            "init",
            "--yes",
            "--config",
            config_path.to_str().unwrap(),
        ])
        .current_dir(&repo_path);

        cmd.assert().success();
    }
}

mod init_command {
    use super::*;

    #[test]
    fn test_init_quick() {
        let temp_dir = TempDir::new().unwrap();
        let config_path = temp_dir.path().join("config.toml");

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);

        cmd.assert()
            .success()
            .stdout(predicate::str::contains("initialized successfully"));
    }

    #[test]
    fn test_init_creates_config_file() {
        let temp_dir = TempDir::new().unwrap();
        let config_path = temp_dir.path().join("config.toml");

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);

        cmd.assert().success();

        assert!(config_path.exists(), "Config file should be created");
    }

    #[test]
    fn test_init_in_git_repo() {
        let temp_dir = TempDir::new().unwrap();
        let repo_path = temp_dir.path().to_path_buf();
        create_git_repo(&repo_path);
        configure_git_user(&repo_path);

        let config_path = repo_path.join("test_config.toml");

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()])
            .current_dir(&repo_path);

        cmd.assert().success();
    }

    #[test]
    fn test_init_reset() {
        let temp_dir = TempDir::new().unwrap();
        let config_path = temp_dir.path().join("config.toml");

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);
        cmd.assert().success();

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&[
            "init",
            "--yes",
            "--reset",
            "--config",
            config_path.to_str().unwrap(),
        ]);
        cmd.assert()
            .success()
            .stdout(predicate::str::contains("initialized successfully"));
    }
}

mod profile_command {
    use super::*;

    #[test]
    fn test_profile_list_empty() {
        let temp_dir = TempDir::new().unwrap();
        let config_path = temp_dir.path().join("config.toml");

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&["profile", "list", "--config", config_path.to_str().unwrap()]);

        cmd.assert()
            .success()
            .stdout(predicate::str::contains("No profiles"));
    }

    #[test]
    fn test_profile_list_with_profile() {
        let temp_dir = TempDir::new().unwrap();
        let config_path = temp_dir.path().join("config.toml");

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);
        cmd.assert().success();

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&["profile", "list", "--config", config_path.to_str().unwrap()]);

        cmd.assert()
            .success()
            .stdout(predicate::str::contains("default"));
    }
}

mod config_command {
    use super::*;

    #[test]
    fn test_config_show() {
        let temp_dir = TempDir::new().unwrap();
        let config_path = temp_dir.path().join("config.toml");

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);
        cmd.assert().success();

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&["config", "show", "--config", config_path.to_str().unwrap()]);

        cmd.assert()
            .success()
            .stdout(predicate::str::contains("Configuration"));
    }

    #[test]
    fn test_config_path() {
        let temp_dir = TempDir::new().unwrap();
        let config_path = temp_dir.path().join("config.toml");

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);
        cmd.assert().success();

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&["config", "path", "--config", config_path.to_str().unwrap()]);

        cmd.assert()
            .success()
            .stdout(predicate::str::contains("config.toml"));
    }
}

mod commit_command {
    use super::*;

    #[test]
    fn test_commit_no_repo() {
        let temp_dir = TempDir::new().unwrap();
        let config_path = temp_dir.path().join("config.toml");

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);
        cmd.assert().success();

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&[
            "commit",
            "--dry-run",
            "--yes",
            "--config",
            config_path.to_str().unwrap(),
        ])
        .current_dir(temp_dir.path());

        cmd.assert()
            .failure()
            .stderr(predicate::str::contains("git").or(predicate::str::contains("repository")));
    }

    #[test]
    fn test_commit_no_changes() {
        let temp_dir = TempDir::new().unwrap();
        let repo_path = temp_dir.path().to_path_buf();
        setup_git_repo(&repo_path);

        let config_path = repo_path.join("config.toml");
        init_quicommit(&repo_path, &config_path);

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&[
            "commit",
            "--manual",
            "-m",
            "test: empty",
            "--dry-run",
            "--yes",
            "--config",
            config_path.to_str().unwrap(),
        ])
        .current_dir(&repo_path);

        cmd.assert()
            .success()
            .stdout(predicate::str::contains("Dry run"));
    }

    #[test]
    fn test_commit_with_staged_changes() {
        let temp_dir = TempDir::new().unwrap();
        let repo_path = temp_dir.path().to_path_buf();
        setup_test_repo_with_file(&repo_path, "test.txt", "Hello, World!");

        let config_path = repo_path.join("config.toml");
        init_quicommit(&repo_path, &config_path);

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&[
            "commit",
            "--manual",
            "-m",
            "test: add test file",
            "--dry-run",
            "--yes",
            "--config",
            config_path.to_str().unwrap(),
        ])
        .current_dir(&repo_path);

        cmd.assert()
            .success()
            .stdout(predicate::str::contains("Dry run"));
    }

    #[test]
    fn test_commit_date_mode() {
        let temp_dir = TempDir::new().unwrap();
        let repo_path = temp_dir.path().to_path_buf();
        setup_test_repo_with_file(&repo_path, "daily.txt", "Daily update");

        let config_path = repo_path.join("config.toml");
        init_quicommit(&repo_path, &config_path);

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&[
            "commit",
            "--date",
            "--dry-run",
            "--yes",
            "--config",
            config_path.to_str().unwrap(),
        ])
        .current_dir(&repo_path);

        cmd.assert()
            .success()
            .stdout(predicate::str::contains("Dry run"));
    }

    #[test]
    fn test_commit_with_think_flag() {
        let temp_dir = TempDir::new().unwrap();
        let repo_path = temp_dir.path().to_path_buf();
        setup_test_repo_with_file(&repo_path, "test.txt", "Hello, World!");

        let config_path = repo_path.join("config.toml");
        init_quicommit(&repo_path, &config_path);

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&[
            "commit",
            "--think",
            "--manual",
            "-m",
            "test: think flag",
            "--dry-run",
            "--yes",
            "--config",
            config_path.to_str().unwrap(),
        ])
        .current_dir(&repo_path);

        cmd.assert().success();
    }
}

mod tag_command {
    use super::*;

    #[test]
    fn test_tag_no_repo() {
        let temp_dir = TempDir::new().unwrap();
        let config_path = temp_dir.path().join("config.toml");

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);
        cmd.assert().success();

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&[
            "tag",
            "--dry-run",
            "--yes",
            "--config",
            config_path.to_str().unwrap(),
        ])
        .current_dir(temp_dir.path());

        cmd.assert()
            .failure()
            .stderr(predicate::str::contains("git").or(predicate::str::contains("repository")));
    }

    #[test]
    fn test_tag_list_empty() {
        let temp_dir = TempDir::new().unwrap();
        let repo_path = temp_dir.path().to_path_buf();
        setup_git_repo(&repo_path);

        create_test_file(&repo_path, "test.txt", "content");
        stage_file(&repo_path, "test.txt");
        create_commit(&repo_path, "feat: initial commit");

        let config_path = repo_path.join("config.toml");
        init_quicommit(&repo_path, &config_path);

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&[
            "tag",
            "--name",
            "v0.1.0",
            "--dry-run",
            "--yes",
            "--config",
            config_path.to_str().unwrap(),
        ])
        .current_dir(&repo_path);

        cmd.assert()
            .success()
            .stdout(predicate::str::contains("v0.1.0"));
    }

    #[test]
    fn test_tag_with_think_flag() {
        let temp_dir = TempDir::new().unwrap();
        let repo_path = temp_dir.path().to_path_buf();
        setup_git_repo(&repo_path);

        create_test_file(&repo_path, "test.txt", "content");
        stage_file(&repo_path, "test.txt");
        create_commit(&repo_path, "feat: initial commit");

        let config_path = repo_path.join("config.toml");
        init_quicommit(&repo_path, &config_path);

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&[
            "tag",
            "--think",
            "--name",
            "v0.2.0",
            "--dry-run",
            "--yes",
            "--config",
            config_path.to_str().unwrap(),
        ])
        .current_dir(&repo_path);

        cmd.assert().success();
    }
}

mod changelog_command {
    use super::*;

    #[test]
    fn test_changelog_init() {
        let temp_dir = TempDir::new().unwrap();
        let repo_path = temp_dir.path().to_path_buf();
        setup_git_repo(&repo_path);

        let config_path = repo_path.join("config.toml");
        let changelog_path = repo_path.join("CHANGELOG.md");

        init_quicommit(&repo_path, &config_path);

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&[
            "changelog",
            "--init",
            "--output",
            changelog_path.to_str().unwrap(),
            "--config",
            config_path.to_str().unwrap(),
        ])
        .current_dir(&repo_path);

        cmd.assert().success();

        assert!(changelog_path.exists(), "Changelog file should be created");
    }

    #[test]
    fn test_changelog_dry_run() {
        let temp_dir = TempDir::new().unwrap();
        let repo_path = temp_dir.path().to_path_buf();
        setup_git_repo(&repo_path);

        create_test_file(&repo_path, "test.txt", "content");
        stage_file(&repo_path, "test.txt");
        create_commit(&repo_path, "feat: add feature");

        let config_path = repo_path.join("config.toml");
        init_quicommit(&repo_path, &config_path);

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&[
            "changelog",
            "--dry-run",
            "--yes",
            "--config",
            config_path.to_str().unwrap(),
        ])
        .current_dir(&repo_path);

        cmd.assert().success();
    }
}

mod cross_platform {
    use super::*;

    #[test]
    fn test_path_handling_windows_style() {
        let temp_dir = TempDir::new().unwrap();
        let config_path = temp_dir.path().join("subdir").join("config.toml");

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);

        cmd.assert().success();
        assert!(config_path.exists());
    }

    #[test]
    fn test_config_with_spaces_in_path() {
        let temp_dir = TempDir::new().unwrap();
        let space_dir = temp_dir.path().join("path with spaces");
        fs::create_dir_all(&space_dir).unwrap();
        let config_path = space_dir.join("config.toml");

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);

        cmd.assert().success();
        assert!(config_path.exists());
    }

    #[test]
    fn test_config_with_unicode_path() {
        let temp_dir = TempDir::new().unwrap();
        let unicode_dir = temp_dir.path().join("路径测试");
        fs::create_dir_all(&unicode_dir).unwrap();
        let config_path = unicode_dir.join("config.toml");

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);

        cmd.assert().success();
        assert!(config_path.exists());
    }
}

mod git_operations {
    use super::*;

    #[test]
    fn test_git_repo_detection() {
        let temp_dir = TempDir::new().unwrap();
        let repo_path = temp_dir.path().to_path_buf();
        create_git_repo(&repo_path);
        configure_git_user(&repo_path);

        let git_dir = repo_path.join(".git");
        assert!(git_dir.exists(), ".git directory should exist");
    }

    #[test]
    fn test_git_status_clean() {
        let temp_dir = TempDir::new().unwrap();
        let repo_path = temp_dir.path().to_path_buf();
        create_git_repo(&repo_path);
        configure_git_user(&repo_path);

        let output = std::process::Command::new("git")
            .args(&["status", "--porcelain"])
            .current_dir(&repo_path)
            .output()
            .expect("Failed to run git status");

        assert!(output.status.success());
        assert!(String::from_utf8_lossy(&output.stdout).is_empty());
    }

    #[test]
    fn test_git_commit_creation() {
        let temp_dir = TempDir::new().unwrap();
        let repo_path = temp_dir.path().to_path_buf();
        create_git_repo(&repo_path);
        configure_git_user(&repo_path);

        create_test_file(&repo_path, "test.txt", "content");
        stage_file(&repo_path, "test.txt");
        create_commit(&repo_path, "feat: initial commit");

        let output = std::process::Command::new("git")
            .args(&["log", "--oneline"])
            .current_dir(&repo_path)
            .output()
            .expect("Failed to run git log");

        assert!(output.status.success());
        let log = String::from_utf8_lossy(&output.stdout);
        assert!(log.contains("initial commit"));
    }
}

mod validators {
    use super::*;

    #[test]
    fn test_commit_message_validation() {
        let temp_dir = TempDir::new().unwrap();
        let repo_path = temp_dir.path().to_path_buf();
        setup_test_repo_with_file(&repo_path, "test.txt", "content");

        let config_path = repo_path.join("config.toml");
        init_quicommit(&repo_path, &config_path);

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&[
            "commit",
            "--manual",
            "-m",
            "invalid commit message without type",
            "--dry-run",
            "--yes",
            "--config",
            config_path.to_str().unwrap(),
        ])
        .current_dir(&repo_path);

        cmd.assert()
            .failure()
            .stderr(predicate::str::contains("Invalid").or(predicate::str::contains("format")));
    }

    #[test]
    fn test_valid_conventional_commit() {
        let temp_dir = TempDir::new().unwrap();
        let repo_path = temp_dir.path().to_path_buf();
        setup_test_repo_with_file(&repo_path, "test.txt", "content");

        let config_path = repo_path.join("config.toml");
        init_quicommit(&repo_path, &config_path);

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&[
            "commit",
            "--manual",
            "-m",
            "feat: add new feature",
            "--dry-run",
            "--yes",
            "--config",
            config_path.to_str().unwrap(),
        ])
        .current_dir(&repo_path);

        cmd.assert()
            .success()
            .stdout(predicate::str::contains("Dry run"));
    }
}

mod subcommands {
    use super::*;

    #[test]
    fn test_commit_alias() {
        let temp_dir = TempDir::new().unwrap();
        let repo_path = temp_dir.path().to_path_buf();
        setup_test_repo_with_file(&repo_path, "test.txt", "content");

        let config_path = repo_path.join("config.toml");
        init_quicommit(&repo_path, &config_path);

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&[
            "c",
            "--manual",
            "-m",
            "fix: test",
            "--dry-run",
            "--yes",
            "--config",
            config_path.to_str().unwrap(),
        ])
        .current_dir(&repo_path);

        cmd.assert()
            .success()
            .stdout(predicate::str::contains("Dry run"));
    }

    #[test]
    fn test_init_alias() {
        let temp_dir = TempDir::new().unwrap();
        let config_path = temp_dir.path().join("config.toml");

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&["i", "--yes", "--config", config_path.to_str().unwrap()]);

        cmd.assert()
            .success()
            .stdout(predicate::str::contains("initialized successfully"));
    }

    #[test]
    fn test_profile_alias() {
        let temp_dir = TempDir::new().unwrap();
        let config_path = temp_dir.path().join("config.toml");

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);
        cmd.assert().success();

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&["p", "list", "--config", config_path.to_str().unwrap()]);

        cmd.assert()
            .success()
            .stdout(predicate::str::contains("default"));
    }
}

mod edge_cases {
    use super::*;

    #[test]
    fn test_config_file_not_found() {
        let temp_dir = TempDir::new().unwrap();
        let non_existent_config = temp_dir.path().join("non_existent_config.toml");

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&[
            "config",
            "show",
            "--config",
            non_existent_config.to_str().unwrap(),
        ]);

        cmd.assert()
            .success()
            .stdout(predicate::str::contains("QuiCommit Configuration"))
            .stdout(predicate::str::contains("Default profile: (none)"))
            .stdout(predicate::str::contains("Profiles: 0"));
    }

    #[test]
    fn test_invalid_git_repo() {
        let temp_dir = TempDir::new().unwrap();
        let repo_path = temp_dir.path().to_path_buf();

        let config_path = repo_path.join("config.toml");
        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);
        cmd.assert().success();

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&[
            "commit",
            "--dry-run",
            "--yes",
            "--config",
            config_path.to_str().unwrap(),
        ])
        .current_dir(&repo_path);

        cmd.assert()
            .failure()
            .stderr(predicate::str::contains("git").or(predicate::str::contains("repository")));
    }

    #[test]
    fn test_empty_commit_message() {
        let temp_dir = TempDir::new().unwrap();
        let repo_path = temp_dir.path().to_path_buf();
        setup_test_repo_with_file(&repo_path, "test.txt", "content");

        let config_path = repo_path.join("config.toml");
        init_quicommit(&repo_path, &config_path);

        let mut cmd = cargo_bin_cmd!("quicommit");
        cmd.args(&[
            "commit",
            "--manual",
            "-m",
            "",
            "--dry-run",
            "--yes",
            "--config",
            config_path.to_str().unwrap(),
        ])
        .current_dir(&repo_path);

        cmd.assert().failure().stderr(predicate::str::contains(
            "Invalid conventional commit format",
        ));
    }
}
