use std::path::PathBuf;

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

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

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

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

        let source_dir = PathBuf::from(&source_storage_path);
        if !source_dir.exists() {
            return Err(GitError::Internal(format!(
                "source repository does not exist: {}",
                source_dir.display()
            )));
        }
        let output = duct::cmd(
            "git",
            &[
                "clone",
                "--bare",
                source_dir.to_string_lossy().as_ref(),
                target_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(&target_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: target_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(),
            });
        }
        let remote_output = bare.git_command_trusted(vec![
            "remote".to_string(),
            "add".to_string(),
            "upstream".to_string(),
            source_dir.to_string_lossy().to_string(),
        ])?;
        if !remote_output.success {
            tracing::warn!(
                "failed to add upstream remote: {}",
                remote_output.stderr_lossy()
            );
        }
        if let Some(desc) = &params.description {
            let desc_path = target_dir.join("description");
            std::fs::write(&desc_path, desc)?;
        }
        if params.enable_lfs {
            let gitattributes_path = target_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)?;
        }

        Ok(target_dir.to_string_lossy().to_string())
    }
}
