use std::collections::HashSet;

use db::{database::AppDatabase, sqlx};
use model::repos::RepoLfsObjectModel;
use uuid::Uuid;

use crate::{bare::GitBare, errors::GitError};
pub async fn sync_lfs_objects(
    db: &AppDatabase,
    bare: &GitBare,
    repo_id: Uuid,
) -> Result<(), GitError> {
    let pool = db.writer();

    let existing: Vec<RepoLfsObjectModel> = sqlx::query_as::<_, RepoLfsObjectModel>(
        "SELECT repo, oid, size_bytes, storage_key, created_at FROM repo_lfs_object WHERE repo = $1"
    )
        .bind(repo_id)
        .fetch_all(pool)
        .await
        .map_err(|e| GitError::Internal(format!("failed to query lfs objects: {}", e)))?;

    let mut existing_oids: HashSet<String> =
        existing.into_iter().map(|o| o.oid).collect();

    let lfs_dir = bare.bare_dir.join(".lfs").join("objects");
    if !lfs_dir.exists() {
        if !existing_oids.is_empty() {
            let oids_vec: Vec<String> = existing_oids.into_iter().collect();
            sqlx::query(
                "DELETE FROM repo_lfs_object WHERE repo = $1 AND oid = ANY($2)",
            )
            .bind(repo_id)
            .bind(&oids_vec)
            .execute(pool)
            .await
            .map_err(|e| {
                GitError::Internal(format!(
                    "failed to delete stale lfs objects: {}",
                    e
                ))
            })?;
        }
        return Ok(());
    }

    let now = chrono::Utc::now();
    let mut new_objects: Vec<(String, i64, String)> = Vec::new();

    if let Ok(prefix_entries) = std::fs::read_dir(&lfs_dir) {
        for prefix_entry in prefix_entries.flatten() {
            if let Ok(oid_entries) = std::fs::read_dir(prefix_entry.path()) {
                for oid_entry in oid_entries.flatten() {
                    let oid_str =
                        oid_entry.file_name().to_string_lossy().to_string();
                    if existing_oids.contains(&oid_str) {
                        existing_oids.remove(&oid_str);
                        continue;
                    }

                    let path = oid_entry.path();
                    let size_bytes = match std::fs::metadata(&path) {
                        Ok(meta) => meta.len() as i64,
                        Err(_) => continue,
                    };
                    let storage_key = path.to_string_lossy().to_string();

                    new_objects.push((oid_str, size_bytes, storage_key));
                }
            }
        }
    }

    for (oid, size_bytes, storage_key) in &new_objects {
        sqlx::query(
            "INSERT INTO repo_lfs_object (repo, oid, size_bytes, storage_key, created_at) VALUES ($1, $2, $3, $4, $5)"
        )
            .bind(repo_id)
            .bind(oid)
            .bind(*size_bytes)
            .bind(storage_key)
            .bind(now)
            .execute(pool)
            .await
            .map_err(|e| GitError::Internal(format!("failed to insert lfs object: {}", e)))?;
    }

    if !existing_oids.is_empty() {
        let oids_vec: Vec<String> = existing_oids.into_iter().collect();
        sqlx::query(
            "DELETE FROM repo_lfs_object WHERE repo = $1 AND oid = ANY($2)",
        )
        .bind(repo_id)
        .bind(&oids_vec)
        .execute(pool)
        .await
        .map_err(|e| {
            GitError::Internal(format!(
                "failed to delete stale lfs objects: {}",
                e
            ))
        })?;
    }

    Ok(())
}
