use crate::GitError;
use crate::hook::sync::HookMetaDataSync;
use db::database::AppTransaction;
use models::system::notify;
use sea_orm::*;
use std::collections::HashMap;
use std::process::Command;

impl HookMetaDataSync {
    pub async fn run_fsck_and_rollback_if_corrupt(
        &self,
        txn: &AppTransaction,
    ) -> Result<(), GitError> {
        let snapshot = self.snapshot_refs();
        let storage_path = self.repo.storage_path.clone();

        let fsck_errors = tokio::task::spawn_blocking(move || {
            let output = Command::new("git")
                .arg("-C")
                .arg(&storage_path)
                .arg("fsck")
                .arg("--full")
                .output()
                .map_err(|e| GitError::IoError(format!("git fsck failed: {}", e)))?;

            if !output.status.success() {
                let stderr = String::from_utf8_lossy(&output.stderr).to_string();
                let stdout = String::from_utf8_lossy(&output.stdout).to_string();
                tracing::warn!(
                    "git fsck failed code={:?} stdout={} stderr={}",
                    output.status.code(),
                    stdout,
                    stderr
                );
                return Ok(Some(format!("{}\n{}", stdout, stderr)));
            }
            Ok::<Option<String>, GitError>(None)
        })
        .await
        .map_err(|e| GitError::Internal(format!("spawn_blocking join error: {}", e)))??;

        if let Some(errors) = fsck_errors {
            self.rollback_refs(&snapshot).await;

            let notification = notify::ActiveModel {
                user: Set(self.repo.created_by),
                title: Set(format!(
                    "Repository sync rollback triggered: {}",
                    self.repo.repo_name
                )),
                description: Set(Some("Repository integrity check failed".to_string())),
                content: Set(format!(
                    "Repository {} sync failed and has been rolled back.\nError: {}",
                    self.repo.repo_name, errors
                )),
                kind: Set(1),
                created_at: Set(chrono::Utc::now()),
                ..Default::default()
            };

            notify::Entity::insert(notification)
                .exec(txn)
                .await
                .map_err(|e| GitError::IoError(format!("failed to insert notification: {}", e)))?;

            return Err(GitError::Internal(format!(
                "repository corruption detected: {}",
                errors
            )));
        }

        Ok(())
    }

    fn snapshot_refs(&self) -> HashMap<String, String> {
        let mut snapshot = HashMap::new();
        let repo = self.domain.repo();

        if let Ok(refs) = repo.references() {
            for r in refs.flatten() {
                let name = match r.name() {
                    Some(n) => n.to_string(),
                    None => continue,
                };
                let oid = match r.target() {
                    Some(o) => o.to_string(),
                    None => continue,
                };
                if name.starts_with("refs/heads/") || name.starts_with("refs/tags/") {
                    snapshot.insert(name, oid);
                }
            }
        }
        snapshot
    }

    async fn rollback_refs(&self, snapshot: &HashMap<String, String>) {
        let storage_path = self.repo.storage_path.clone();
        let refs: Vec<(String, String)> = snapshot
            .iter()
            .map(|(k, v)| (k.clone(), v.clone()))
            .collect();

        let _ = tokio::task::spawn_blocking(move || {
            for (ref_name, oid) in &refs {
                // git update-ref -m <msg> <ref> <new-sha> — no old-sha in rollback
                let status = Command::new("git")
                    .arg("-C")
                    .arg(&storage_path)
                    .arg("update-ref")
                    .arg("-m")
                    .arg("rollback: integrity check failed")
                    .arg(ref_name)
                    .arg(oid)
                    .status();

                match status {
                    Ok(s) if s.success() => {
                        tracing::info!("rolled back ref ref_name={} oid={}", ref_name, oid);
                    }
                    Ok(s) => {
                        tracing::error!(
                            "failed to rollback ref ref_name={} code={:?}",
                            ref_name,
                            s.code()
                        );
                    }
                    Err(e) => {
                        tracing::error!("failed to rollback ref ref_name={} error={}", ref_name, e);
                    }
                }
            }
        })
        .await;
    }
}
