use crate::sync::api::{FileEntry, MiniCloudApi};
use sha2::{Digest, Sha256};
use std::collections::HashMap;
use std::path::{Path, PathBuf};

/// Compare local files with server tree and determine actions
pub struct SyncEngine {
    pub sync_dir: PathBuf,
    pub api: MiniCloudApi,
    last_sync: Option<String>,
}

#[derive(Debug, Clone)]
pub enum SyncAction {
    Download { id: i64, name: String, server_path: String },
    Upload { local_path: PathBuf, parent_id: Option<i64> },
    CreateFolder { name: String, parent_id: Option<i64> },
    Delete { local_path: PathBuf },
    Conflict { local_path: PathBuf, server_id: i64, locked_by: String },
}

impl SyncEngine {
    pub fn new(sync_dir: PathBuf, api: MiniCloudApi) -> Self {
        Self { sync_dir, api, last_sync: None }
    }

    /// Full sync: compare server tree with local filesystem
    pub async fn full_sync(&mut self) -> Result<Vec<String>, String> {
        let server_tree = self.api.get_sync_tree().await?;
        let mut log = Vec::new();

        // Download missing/changed files from server
        self.sync_download_recursive(&server_tree, &self.sync_dir.clone(), &mut log).await;

        // Upload local files not on server
        self.sync_upload_recursive(&server_tree, &self.sync_dir.clone(), None, &mut log).await;

        // Update last sync time
        self.last_sync = Some(chrono::Utc::now().to_rfc3339());

        Ok(log)
    }

    /// Delta sync: only changes since last sync
    pub async fn delta_sync(&mut self) -> Result<Vec<String>, String> {
        let since = self.last_sync.clone().unwrap_or_else(|| "2000-01-01T00:00:00Z".to_string());
        let changes = self.api.get_changes(&since).await?;
        let mut log = Vec::new();

        for entry in &changes.changes {
            let rel_path = self.sync_dir.join(&entry.name);

            if entry.is_folder {
                if !rel_path.exists() {
                    std::fs::create_dir_all(&rel_path).ok();
                    log.push(format!("Ordner erstellt: {}", entry.name));
                }
            } else {
                // Check if locked
                if entry.locked.unwrap_or(false) {
                    log.push(format!("Uebersprungen (gesperrt von {}): {}",
                        entry.locked_by.as_deref().unwrap_or("?"), entry.name));
                    continue;
                }

                // Download if checksum differs
                let needs_download = if rel_path.exists() {
                    let local_hash = compute_file_hash(&rel_path);
                    local_hash != entry.checksum.as_deref().unwrap_or("")
                } else {
                    true
                };

                if needs_download {
                    match self.api.download_file(entry.id, &rel_path).await {
                        Ok(_) => log.push(format!("Heruntergeladen: {}", entry.name)),
                        Err(e) => log.push(format!("Fehler {}: {}", entry.name, e)),
                    }
                }
            }
        }

        self.last_sync = Some(changes.server_time);
        Ok(log)
    }

    async fn sync_download_recursive(&self, entries: &[FileEntry], local_dir: &Path, log: &mut Vec<String>) {
        for entry in entries {
            let local_path = local_dir.join(&entry.name);

            if entry.is_folder {
                std::fs::create_dir_all(&local_path).ok();
                if let Some(children) = &entry.children {
                    Box::pin(self.sync_download_recursive(children, &local_path, log)).await;
                }
            } else {
                // Skip locked files
                if entry.locked.unwrap_or(false) {
                    continue;
                }

                let needs_download = if local_path.exists() {
                    let local_hash = compute_file_hash(&local_path);
                    local_hash != entry.checksum.as_deref().unwrap_or("")
                } else {
                    true
                };

                if needs_download {
                    match self.api.download_file(entry.id, &local_path).await {
                        Ok(_) => log.push(format!("Heruntergeladen: {}", entry.name)),
                        Err(e) => log.push(format!("Fehler {}: {}", entry.name, e)),
                    }
                }
            }
        }
    }

    async fn sync_upload_recursive(&self, server_entries: &[FileEntry], local_dir: &Path,
                                    parent_id: Option<i64>, log: &mut Vec<String>) {
        let server_names: HashMap<String, &FileEntry> = server_entries.iter()
            .map(|e| (e.name.clone(), e))
            .collect();

        let entries = match std::fs::read_dir(local_dir) {
            Ok(e) => e,
            Err(_) => return,
        };

        for entry in entries.flatten() {
            let name = entry.file_name().to_string_lossy().to_string();
            let path = entry.path();

            // Skip hidden files and sync metadata
            if name.starts_with('.') || name == ".minicloud_sync" {
                continue;
            }

            if path.is_dir() {
                if let Some(server_entry) = server_names.get(&name) {
                    // Folder exists on server, recurse
                    if let Some(children) = &server_entry.children {
                        Box::pin(self.sync_upload_recursive(
                            children, &path, Some(server_entry.id), log
                        )).await;
                    }
                } else {
                    // Folder doesn't exist on server, create + upload contents
                    match self.api.create_folder(&name, parent_id).await {
                        Ok(folder) => {
                            log.push(format!("Ordner erstellt: {}", name));
                            Box::pin(self.sync_upload_recursive(
                                &[], &path, Some(folder.id), log
                            )).await;
                        }
                        Err(e) => log.push(format!("Ordner-Fehler {}: {}", name, e)),
                    }
                }
            } else {
                // File
                let needs_upload = if let Some(server_entry) = server_names.get(&name) {
                    // Check if local version is newer
                    let local_hash = compute_file_hash(&path);
                    local_hash != server_entry.checksum.as_deref().unwrap_or("")
                } else {
                    true // New file
                };

                if needs_upload {
                    // Check if file is locked on server
                    if let Some(server_entry) = server_names.get(&name) {
                        if server_entry.locked.unwrap_or(false) {
                            log.push(format!("Zurueckgehalten (gesperrt): {}", name));
                            continue;
                        }
                    }

                    match self.api.upload_file(&path, parent_id).await {
                        Ok(_) => log.push(format!("Hochgeladen: {}", name)),
                        Err(e) => log.push(format!("Upload-Fehler {}: {}", name, e)),
                    }
                }
            }
        }
    }
}

pub fn compute_file_hash(path: &Path) -> String {
    let data = match std::fs::read(path) {
        Ok(d) => d,
        Err(_) => return String::new(),
    };
    let mut hasher = Sha256::new();
    hasher.update(&data);
    format!("{:x}", hasher.finalize())
}
