use crate::GitError;
use crate::hook::sync::HookMetaDataSync;
use db::database::AppTransaction;
use models::repos::repo;
use models::repos::repo_branch;
use sea_orm::prelude::Expr;
use sea_orm::*;
use std::collections::HashSet;

impl HookMetaDataSync {
    pub async fn sync_refs(&self, txn: &AppTransaction) -> Result<(), GitError> {
        let repo_id = self.repo.id;
        let now = chrono::Utc::now();

        let existing: Vec<repo_branch::Model> = repo_branch::Entity::find()
            .filter(repo_branch::Column::Repo.eq(repo_id))
            .all(txn)
            .await
            .map_err(|e| GitError::IoError(format!("failed to query branches: {}", e)))?;
        let mut existing_names: HashSet<String> = existing.iter().map(|r| r.name.clone()).collect();

        let references = self
            .domain
            .repo()
            .references()
            .map_err(|e| GitError::Internal(e.to_string()))?;

        // Auto-detect first local branch when default_branch is empty
        let mut auto_detected_branch: Option<String> = None;

        for reference in references {
            let reference = reference.map_err(|e| GitError::Internal(e.to_string()))?;
            let name = reference
                .name()
                .ok_or_else(|| GitError::RefNotFound("unnamed ref".into()))?
                .to_string();
            let shorthand = reference.shorthand().unwrap_or("").to_string();

            let target_oid = match reference.target() {
                Some(oid) => oid.to_string(),
                None => continue,
            };

            let is_branch = reference.is_branch();
            let is_remote = reference.is_remote();

            // Detect first local branch if no default is set
            if self.repo.default_branch.is_empty()
                && is_branch
                && !is_remote
                && auto_detected_branch.is_none()
            {
                auto_detected_branch = Some(shorthand.clone());
            }

            let upstream = if is_branch && !is_remote {
                reference
                    .shorthand()
                    .map(|short| format!("refs/remotes/{{}}/{}", short))
            } else {
                None
            };

            if existing_names.contains(&name) {
                existing_names.remove(&name);
                repo_branch::Entity::update_many()
                    .filter(repo_branch::Column::Repo.eq(repo_id))
                    .filter(repo_branch::Column::Name.eq(&name))
                    .col_expr(repo_branch::Column::Oid, Expr::value(&target_oid))
                    .col_expr(repo_branch::Column::Upstream, Expr::value(upstream))
                    .col_expr(
                        repo_branch::Column::Head,
                        Expr::value(is_branch && shorthand == self.repo.default_branch),
                    )
                    .col_expr(repo_branch::Column::UpdatedAt, Expr::value(now))
                    .exec(txn)
                    .await
                    .map_err(|e| GitError::IoError(format!("failed to update branch: {}", e)))?;
            } else {
                let new_branch = repo_branch::ActiveModel {
                    repo: Set(repo_id),
                    name: Set(name),
                    oid: Set(target_oid),
                    upstream: Set(upstream),
                    head: Set(is_branch && shorthand == self.repo.default_branch),
                    created_at: Set(now),
                    updated_at: Set(now),
                    ..Default::default()
                };
                new_branch
                    .insert(txn)
                    .await
                    .map_err(|e| GitError::IoError(format!("failed to insert branch: {}", e)))?;
            }
        }

        if !existing_names.is_empty() {
            repo_branch::Entity::delete_many()
                .filter(repo_branch::Column::Repo.eq(repo_id))
                .filter(repo_branch::Column::Name.is_in(existing_names))
                .exec(txn)
                .await
                .map_err(|e| {
                    GitError::IoError(format!("failed to delete stale branches: {}", e))
                })?;
        }

        // Persist auto-detected default branch and update head flags
        if let Some(ref branch_name) = auto_detected_branch {
            // 1. Update the repo's default_branch
            repo::Entity::update_many()
                .filter(repo::Column::Id.eq(repo_id))
                .col_expr(
                    repo::Column::DefaultBranch,
                    Expr::value(branch_name.clone()),
                )
                .exec(txn)
                .await
                .map_err(|e| GitError::IoError(format!("failed to set default branch: {}", e)))?;

            // 2. Clear head on all branches
            repo_branch::Entity::update_many()
                .filter(repo_branch::Column::Repo.eq(repo_id))
                .col_expr(repo_branch::Column::Head, Expr::value(false))
                .exec(txn)
                .await
                .map_err(|e| GitError::IoError(format!("failed to clear head flags: {}", e)))?;

            // 3. Set head = true for the detected branch (it was inserted above)
            repo_branch::Entity::update_many()
                .filter(repo_branch::Column::Repo.eq(repo_id))
                .filter(repo_branch::Column::Name.eq(branch_name))
                .col_expr(repo_branch::Column::Head, Expr::value(true))
                .exec(txn)
                .await
                .map_err(|e| GitError::IoError(format!("failed to set head flag: {}", e)))?;
        }

        Ok(())
    }
}
