use std::path::PathBuf;

use crate::{
    bare::GitBare,
    errors::{GitError, GitResult},
};

pub struct InitRepositoriesParams {
    pub namespace: String,
    pub repo_name: String,
    pub default_branch: String,
    pub description: Option<String>,
    pub initialize_with_readme: bool,
    pub enable_lfs: bool,
}

pub struct CloneRepoParams {
    pub namespace: String,
    pub repo_name: String,
    pub source_url: String,
}

impl CloneRepoParams {
    pub async fn clone_bare(
        storage_root: String,
        params: CloneRepoParams,
    ) -> GitResult<String> {
        let repo_dir = PathBuf::from(&storage_root)
            .join("repo")
            .join(&params.namespace)
            .join(&params.repo_name);

        if repo_dir.exists() {
            return Err(GitError::Internal(format!(
                "repository directory already exists: {}",
                repo_dir.display()
            )));
        }

        if let Some(parent) = repo_dir.parent() {
            std::fs::create_dir_all(parent)?;
        }

        // Clone as bare repo from source URL
        let output = duct::cmd(
            "git",
            &[
                "clone",
                "--bare",
                &params.source_url,
                repo_dir.to_string_lossy().as_ref(),
            ],
        )
        .stdout_capture()
        .stderr_capture()
        .env("GIT_CONFIG_NOSYSTEM", "1")
        .env("GIT_TERMINAL_PROMPT", "0")
        .unchecked()
        .run()?;

        if !output.status.success() {
            std::fs::remove_dir_all(&repo_dir).ok();
            return Err(GitError::CommandFailed {
                status_code: output.status.code(),
                stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
            });
        }

        Ok(repo_dir.to_string_lossy().to_string())
    }
}

impl InitRepositoriesParams {
    pub async fn init_bare(
        basic_path: String,
        params: InitRepositoriesParams,
    ) -> GitResult<String> {
        let repo_dir = PathBuf::from(&basic_path)
            .join("repo")
            .join(&params.namespace)
            .join(&params.repo_name);

        if repo_dir.exists() {
            return Err(GitError::Internal(format!(
                "repository directory already exists: {}",
                repo_dir.display()
            )));
        }

        std::fs::create_dir_all(&repo_dir)?;
        let output = duct::cmd("git", &["init", "--bare"])
            .dir(&repo_dir)
            .stdout_capture()
            .stderr_capture()
            .env("GIT_CONFIG_NOSYSTEM", "1")
            .env("GIT_TERMINAL_PROMPT", "0")
            .unchecked()
            .run()?;

        if !output.status.success() {
            std::fs::remove_dir_all(&repo_dir).ok();
            return Err(GitError::CommandFailed {
                status_code: output.status.code(),
                stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
            });
        }

        let bare = GitBare {
            bare_dir: repo_dir.clone(),
        };
        let symref_output = bare.git_command_trusted(vec![
            "symbolic-ref".to_string(),
            "HEAD".to_string(),
            format!("refs/heads/{}", params.default_branch),
        ])?;
        if !symref_output.success {
            return Err(GitError::CommandFailed {
                status_code: symref_output.status_code,
                stderr: symref_output.stderr_lossy(),
            });
        }
        if let Some(desc) = &params.description {
            let desc_path = repo_dir.join("description");
            std::fs::write(&desc_path, desc)?;
        }
        if params.enable_lfs {
            let gitattributes_path = repo_dir.join("info").join("attributes");
            if let Some(parent) = gitattributes_path.parent() {
                std::fs::create_dir_all(parent)?;
            }
            let lfs_attributes = "*.psd filter=lfs diff=lfs merge=lfs -text\n\
                                  *.zip filter=lfs diff=lfs merge=lfs -text\n\
                                  *.tar filter=lfs diff=lfs merge=lfs -text\n\
                                  *.gz filter=lfs diff=lfs merge=lfs -text\n\
                                  *.mp4 filter=lfs diff=lfs merge=lfs -text\n\
                                  *.mov filter=lfs diff=lfs merge=lfs -text\n";
            std::fs::write(&gitattributes_path, lfs_attributes)?;
        }
        if params.initialize_with_readme {
            init_initial_commit(&bare, &params)?;
        }

        Ok(repo_dir.to_string_lossy().to_string())
    }
}

fn duct_output_to_error(output: &std::process::Output) -> GitError {
    GitError::CommandFailed {
        status_code: output.status.code(),
        stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
    }
}

fn init_initial_commit(
    bare: &GitBare,
    params: &InitRepositoriesParams,
) -> GitResult<()> {
    let tmp_dir = bare.bare_dir.with_extension("tmp-init");
    let clone_output = duct::cmd(
        "git",
        &[
            "clone",
            bare.bare_dir.to_string_lossy().as_ref(),
            tmp_dir.to_string_lossy().as_ref(),
        ],
    )
    .stdout_capture()
    .stderr_capture()
    .unchecked()
    .run()?;

    if !clone_output.status.success() {
        return Err(duct_output_to_error(&clone_output));
    }
    let checkout_output =
        duct::cmd("git", &["checkout", "-b", &params.default_branch])
            .dir(&tmp_dir)
            .stdout_capture()
            .stderr_capture()
            .unchecked()
            .run()?;

    if !checkout_output.status.success() {
        std::fs::remove_dir_all(&tmp_dir).ok();
        return Err(duct_output_to_error(&checkout_output));
    }
    let readme_content = format!(
        "# {}\n\n{}",
        params.repo_name,
        params.description.as_deref().unwrap_or("")
    );
    let readme_path = tmp_dir.join("README.md");
    std::fs::write(&readme_path, readme_content)?;
    let add_output = duct::cmd("git", &["add", "README.md"])
        .dir(&tmp_dir)
        .stdout_capture()
        .stderr_capture()
        .unchecked()
        .run()?;

    if !add_output.status.success() {
        std::fs::remove_dir_all(&tmp_dir).ok();
        return Err(duct_output_to_error(&add_output));
    }
    let commit_output = duct::cmd("git", &["commit", "-m", "Initial commit"])
        .dir(&tmp_dir)
        .stdout_capture()
        .stderr_capture()
        .env("GIT_CONFIG_NOSYSTEM", "1")
        .env("GIT_COMMITTER_NAME", "panda")
        .env("GIT_COMMITTER_EMAIL", "panda@gitdata.ai")
        .env("GIT_AUTHOR_NAME", "panda")
        .env("GIT_AUTHOR_EMAIL", "panda@gitdata.ai")
        .unchecked()
        .run()?;

    if !commit_output.status.success() {
        std::fs::remove_dir_all(&tmp_dir).ok();
        return Err(duct_output_to_error(&commit_output));
    }
    let push_output =
        duct::cmd("git", &["push", "origin", &params.default_branch])
            .dir(&tmp_dir)
            .stdout_capture()
            .stderr_capture()
            .unchecked()
            .run()?;
    std::fs::remove_dir_all(&tmp_dir).ok();

    if !push_output.status.success() {
        return Err(duct_output_to_error(&push_output));
    }

    Ok(())
}
