use crate::AppService;
use crate::error::AppError;
use crate::git::MergeOptions;
use models::repos::repo;
use sea_orm::*;
use serde::{Deserialize, Serialize};
use session::Session;

#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct DescriptionQuery {
    pub description: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct DescriptionResponse {
    pub description: String,
}
#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct ConfigGetQuery {
    pub key: String,
}
#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct ConfigSetRequest {
    pub key: String,
    pub value: String,
}
#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct ConfigDeleteQuery {
    pub key: String,
}
#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct ConfigEntriesQuery {
    pub prefix: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct ConfigEntryResponse {
    pub name: String,
    pub value: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct ConfigSnapshotResponse {
    pub entries: Vec<ConfigEntryResponse>,
}
impl From<git::ConfigEntry> for ConfigEntryResponse {
    fn from(e: git::ConfigEntry) -> Self {
        Self {
            name: e.name,
            value: e.value,
        }
    }
}
impl From<git::ConfigSnapshot> for ConfigSnapshotResponse {
    fn from(s: git::ConfigSnapshot) -> Self {
        Self {
            entries: s
                .entries
                .into_iter()
                .map(ConfigEntryResponse::from)
                .collect(),
        }
    }
}
#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct GitUpdateRepoRequest {
    pub default_branch: Option<String>,
}
#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
pub struct ConfigBoolResponse {
    pub key: String,
    pub value: bool,
}
#[derive(Debug, Clone, Deserialize)]
pub struct MergeAnalysisQuery {
    pub their_oid: String,
}
#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct MergeRefAnalysisQuery {
    pub ref_name: String,
    pub their_oid: String,
}
#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct MergeCommitsRequest {
    pub local_oid: String,
    pub remote_oid: String,
    #[serde(default)]
    pub find_renames: bool,
    #[serde(default)]
    pub fail_on_conflict: bool,
    #[serde(default)]
    pub skip_reuc: bool,
    #[serde(default)]
    pub no_recursive: bool,
    #[serde(default = "default_rename_threshold")]
    pub rename_threshold: u32,
    #[serde(default)]
    pub target_limit: u32,
    #[serde(default)]
    pub recursion_limit: u32,
}
fn default_rename_threshold() -> u32 {
    50
}
#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct MergeTreesRequest {
    pub ancestor_oid: String,
    pub our_oid: String,
    pub their_oid: String,
    #[serde(default)]
    pub find_renames: bool,
    #[serde(default)]
    pub fail_on_conflict: bool,
    #[serde(default)]
    pub skip_reuc: bool,
    #[serde(default)]
    pub no_recursive: bool,
    #[serde(default = "default_rename_threshold")]
    pub rename_threshold: u32,
    #[serde(default)]
    pub target_limit: u32,
    #[serde(default)]
    pub recursion_limit: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct MergeAnalysisResponse {
    pub analysis: MergeAnalysisResultInner,
    pub preference: MergePreferenceResultInner,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct MergeAnalysisResultInner {
    pub is_none: bool,
    pub is_normal: bool,
    pub is_up_to_date: bool,
    pub is_fast_forward: bool,
    pub is_unborn: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct MergePreferenceResultInner {
    pub is_none: bool,
    pub is_no_fast_forward: bool,
    pub is_fastforward_only: bool,
}
impl From<git::MergeAnalysisResult> for MergeAnalysisResultInner {
    fn from(r: git::MergeAnalysisResult) -> Self {
        Self {
            is_none: r.is_none,
            is_normal: r.is_normal,
            is_up_to_date: r.is_up_to_date,
            is_fast_forward: r.is_fast_forward,
            is_unborn: r.is_unborn,
        }
    }
}
impl From<git::MergePreferenceResult> for MergePreferenceResultInner {
    fn from(r: git::MergePreferenceResult) -> Self {
        Self {
            is_none: r.is_none,
            is_no_fast_forward: r.is_no_fast_forward,
            is_fastforward_only: r.is_fastforward_only,
        }
    }
}
#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
pub struct MergeheadInfoResponse {
    pub oid: String,
}
impl From<git::MergeheadInfo> for MergeheadInfoResponse {
    fn from(h: git::MergeheadInfo) -> Self {
        Self {
            oid: h.oid.to_string(),
        }
    }
}
impl AppService {
    pub async fn git_description_get(
        &self,
        namespace: String,
        repo_name: String,
        ctx: &Session,
    ) -> Result<DescriptionResponse, AppError> {
        let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
        let domain = git::GitDomain::from_model(repo)?;
        let description = domain.description_get()?;
        Ok(DescriptionResponse { description })
    }

    pub async fn git_description_set(
        &self,
        namespace: String,
        repo_name: String,
        query: DescriptionQuery,
        ctx: &Session,
    ) -> Result<DescriptionResponse, AppError> {
        let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
        let domain = git::GitDomain::from_model(repo)?;
        domain.description_set(&query.description)?;
        Ok(DescriptionResponse {
            description: query.description,
        })
    }

    pub async fn git_description_reset(
        &self,
        namespace: String,
        repo_name: String,
        ctx: &Session,
    ) -> Result<DescriptionResponse, AppError> {
        let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
        let domain = git::GitDomain::from_model(repo)?;
        domain.description_reset()?;
        Ok(DescriptionResponse {
            description: "Unnamed repository".to_string(),
        })
    }

    pub async fn git_description_exists(
        &self,
        namespace: String,
        repo_name: String,
        ctx: &Session,
    ) -> Result<bool, AppError> {
        let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
        let domain = git::GitDomain::from_model(repo)?;
        Ok(domain.description_exists())
    }

    pub async fn git_config_entries(
        &self,
        namespace: String,
        repo_name: String,
        query: ConfigEntriesQuery,
        ctx: &Session,
    ) -> Result<ConfigSnapshotResponse, AppError> {
        let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
        let domain = git::GitDomain::from_model(repo)?;
        let snapshot = domain
            .config_entries(query.prefix.as_deref())
            .map_err(AppError::from)?;
        Ok(ConfigSnapshotResponse::from(snapshot))
    }

    pub async fn git_config_get(
        &self,
        namespace: String,
        repo_name: String,
        query: ConfigGetQuery,
        ctx: &Session,
    ) -> Result<Option<String>, AppError> {
        let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
        let domain = git::GitDomain::from_model(repo)?;
        domain.config_get(&query.key).map_err(AppError::from)
    }

    pub async fn git_config_set(
        &self,
        namespace: String,
        repo_name: String,
        request: ConfigSetRequest,
        ctx: &Session,
    ) -> Result<(), AppError> {
        let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
        let domain = git::GitDomain::from_model(repo)?;
        domain
            .config_set(&request.key, &request.value)
            .map_err(AppError::from)
    }

    pub async fn git_config_delete(
        &self,
        namespace: String,
        repo_name: String,
        query: ConfigDeleteQuery,
        ctx: &Session,
    ) -> Result<(), AppError> {
        let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
        let domain = git::GitDomain::from_model(repo)?;
        domain.config_delete(&query.key).map_err(AppError::from)
    }

    pub async fn git_config_has(
        &self,
        namespace: String,
        repo_name: String,
        query: ConfigGetQuery,
        ctx: &Session,
    ) -> Result<ConfigBoolResponse, AppError> {
        let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
        let domain = git::GitDomain::from_model(repo)?;
        let exists = domain.config_has(&query.key).map_err(AppError::from)?;
        Ok(ConfigBoolResponse {
            key: query.key,
            value: exists,
        })
    }

    pub async fn git_merge_analysis(
        &self,
        namespace: String,
        repo_name: String,
        query: MergeAnalysisQuery,
        ctx: &Session,
    ) -> Result<MergeAnalysisResponse, AppError> {
        let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
        let domain = git::GitDomain::from_model(repo)?;
        let their_oid = git::CommitOid::new(&query.their_oid);
        let (analysis, preference) = domain.merge_analysis(&their_oid).map_err(AppError::from)?;
        Ok(MergeAnalysisResponse {
            analysis: MergeAnalysisResultInner::from(analysis),
            preference: MergePreferenceResultInner::from(preference),
        })
    }

    pub async fn git_merge_analysis_for_ref(
        &self,
        namespace: String,
        repo_name: String,
        query: MergeRefAnalysisQuery,
        ctx: &Session,
    ) -> Result<MergeAnalysisResponse, AppError> {
        let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
        let domain = git::GitDomain::from_model(repo)?;
        let their_oid = git::CommitOid::new(&query.their_oid);
        let (analysis, preference) = domain
            .merge_analysis_for_ref(&query.ref_name, &their_oid)
            .map_err(AppError::from)?;
        Ok(MergeAnalysisResponse {
            analysis: MergeAnalysisResultInner::from(analysis),
            preference: MergePreferenceResultInner::from(preference),
        })
    }

    pub async fn git_merge_base(
        &self,
        namespace: String,
        repo_name: String,
        oid1: String,
        oid2: String,
        ctx: &Session,
    ) -> Result<String, AppError> {
        let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
        let domain = git::GitDomain::from_model(repo)?;
        let base = domain
            .merge_base(&git::CommitOid::new(&oid1), &git::CommitOid::new(&oid2))
            .map_err(AppError::from)?;
        Ok(base.to_string())
    }

    pub async fn git_merge_commits(
        &self,
        namespace: String,
        repo_name: String,
        request: MergeCommitsRequest,
        ctx: &Session,
    ) -> Result<(), AppError> {
        let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
        let mut opts = MergeOptions::new();
        opts = opts.find_renames(request.find_renames);
        opts = opts.fail_on_conflict(request.fail_on_conflict);
        opts = opts.skip_reuc(request.skip_reuc);
        opts = opts.no_recursive(request.no_recursive);
        opts = opts.rename_threshold(request.rename_threshold);
        opts = opts.target_limit(request.target_limit);
        opts = opts.recursion_limit(request.recursion_limit);
        let domain = git::GitDomain::from_model(repo)?;
        domain
            .merge_commits(
                &git::CommitOid::new(&request.local_oid),
                &git::CommitOid::new(&request.remote_oid),
                Some(opts),
            )
            .map_err(AppError::from)
    }

    pub async fn git_merge_trees(
        &self,
        namespace: String,
        repo_name: String,
        request: MergeTreesRequest,
        ctx: &Session,
    ) -> Result<(), AppError> {
        let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
        let mut opts = MergeOptions::new();
        opts = opts.find_renames(request.find_renames);
        opts = opts.fail_on_conflict(request.fail_on_conflict);
        opts = opts.skip_reuc(request.skip_reuc);
        opts = opts.no_recursive(request.no_recursive);
        opts = opts.rename_threshold(request.rename_threshold);
        opts = opts.target_limit(request.target_limit);
        opts = opts.recursion_limit(request.recursion_limit);
        let domain = git::GitDomain::from_model(repo)?;
        domain
            .merge_trees(
                &git::CommitOid::new(&request.ancestor_oid),
                &git::CommitOid::new(&request.our_oid),
                &git::CommitOid::new(&request.their_oid),
                Some(opts),
            )
            .map_err(AppError::from)
    }

    pub async fn git_merge_abort(
        &self,
        namespace: String,
        repo_name: String,
        ctx: &Session,
    ) -> Result<(), AppError> {
        let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
        let domain = git::GitDomain::from_model(repo)?;
        domain.merge_abort().map_err(AppError::from)
    }

    pub async fn git_merge_is_in_progress(
        &self,
        namespace: String,
        repo_name: String,
        ctx: &Session,
    ) -> Result<bool, AppError> {
        let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
        let domain = git::GitDomain::from_model(repo)?;
        Ok(domain.merge_is_in_progress())
    }

    pub async fn git_mergehead_list(
        &self,
        namespace: String,
        repo_name: String,
        ctx: &Session,
    ) -> Result<Vec<MergeheadInfoResponse>, AppError> {
        let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
        let mut domain = git::GitDomain::from_model(repo)?;
        let heads = domain.mergehead_list().map_err(AppError::from)?;
        Ok(heads.into_iter().map(MergeheadInfoResponse::from).collect())
    }

    pub async fn git_merge_is_conflicted(
        &self,
        namespace: String,
        repo_name: String,
        ctx: &Session,
    ) -> Result<bool, AppError> {
        let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
        let domain = git::GitDomain::from_model(repo)?;
        Ok(domain.merge_is_conflicted())
    }

    pub async fn git_update_repo(
        &self,
        namespace: String,
        repo_name: String,
        params: GitUpdateRepoRequest,
        ctx: &Session,
    ) -> Result<(), AppError> {
        let repo = self
            .utils_find_repo(namespace.clone(), repo_name.clone(), ctx)
            .await?;
        let txn = self.db.begin().await?;
        let mut active: repo::ActiveModel = repo.clone().into_active_model();
        if let Some(default_branch) = params.default_branch {
            active.default_branch = Set(default_branch);
        }
        active.update(&txn).await?;
        txn.commit().await?;
        Ok(())
    }
}
