use crate::api::Note;
use crate::services::{common, notebooks_service, notes_service, workspaces_service};
use anyhow::Result;
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};

pub struct SyncOptions {
    pub force: bool,
    pub remote_only: Option<String>,
    pub keep_orphans: bool,
    pub rename_files: bool,
}

pub struct SyncResult {
    pub downloaded: usize,
    pub uploaded: usize,
    pub skipped: usize,
    pub deleted: usize,
    pub conflicts: usize,
    pub errors: usize,
}

impl SyncResult {
    pub fn new() -> Self {
        Self {
            downloaded: 0,
            uploaded: 0,
            skipped: 0,
            deleted: 0,
            conflicts: 0,
            errors: 0,
        }
    }

    pub fn add(&mut self, other: &SyncResult) {
        self.downloaded += other.downloaded;
        self.uploaded += other.uploaded;
        self.skipped += other.skipped;
        self.deleted += other.deleted;
        self.conflicts += other.conflicts;
        self.errors += other.errors;
    }
}

pub async fn sync_workspace(
    token: &str,
    workspace_id: &str,
    workspace_dir: &Path,
    options: &SyncOptions,
) -> Result<SyncResult> {
    let workspace = workspaces_service::get_workspace(token, workspace_id).await?;

    fs::create_dir_all(workspace_dir)?;

    // Determine remote_only behavior BEFORE creating metadata
    let remote_only_action = determine_remote_only_action(&options.remote_only, workspace_dir);

    // Create/update workspace metadata file
    create_workspace_metadata(
        workspace_dir,
        &workspace.id,
        &workspace.name,
        &workspace.slug,
    )?;

    println!(
        "Synchronizing workspace '{}' to path: {}",
        workspace.name,
        workspace_dir.display()
    );

    // Get all notebooks in the workspace
    let notebooks = notebooks_service::list_notebooks(token).await?;
    let workspace_notebooks: Vec<_> = notebooks
        .into_iter()
        .filter(|n| n.workspace_id == workspace_id)
        .collect();

    println!("Found {} notebooks in workspace", workspace_notebooks.len());

    let mut result = SyncResult::new();

    for notebook in &workspace_notebooks {
        let notebook_dir = workspace_dir.join(&notebook.slug);
        fs::create_dir_all(&notebook_dir)?;

        // Create/update metadata file
        create_notebook_metadata(&notebook_dir, &notebook.id, &notebook.name, &notebook.slug)?;

        println!("\nNotebook: {} ({})", notebook.name, notebook.slug);

        // Sync notes in this notebook
        let sync_result = sync_notebook_notes(
            token,
            &notebook.id,
            &notebook_dir,
            options.force,
            &remote_only_action,
            options.keep_orphans,
            options.rename_files,
        )
        .await?;

        result.add(&sync_result);
    }

    // Check for new notebooks (folders without metadata.doqium)
    let entries = fs::read_dir(workspace_dir)?;
    for entry in entries {
        let entry = entry?;
        let path = entry.path();

        if !path.is_dir() {
            continue;
        }

        let metadata_path = path.join("metadata.doqium");
        if metadata_path.exists() {
            continue; // Already synced
        }

        // This is a new notebook
        let notebook_slug = path
            .file_name()
            .and_then(|s| s.to_str())
            .ok_or_else(|| anyhow::anyhow!("Invalid notebook folder name"))?;

        println!("\n↑ Creating new notebook: {}", notebook_slug);

        // Create notebook
        let notebook_id =
            notebooks_service::create_notebook(token, workspace_id, notebook_slug).await?;

        // Create metadata file
        create_notebook_metadata(&path, &notebook_id, notebook_slug, notebook_slug)?;

        println!("  ✓ Notebook created: {}", notebook_id);

        // Upload all notes from this folder
        let (uploaded, errors) = upload_notes_from_folder(token, &notebook_id, &path).await?;
        result.uploaded += uploaded;
        result.errors += errors;
    }

    Ok(result)
}

pub async fn sync_notebook(
    token: &str,
    notebook_id: &str,
    notebook_dir: &Path,
    options: &SyncOptions,
) -> Result<SyncResult> {
    fs::create_dir_all(notebook_dir)?;

    let remote_only_action = determine_remote_only_action(&options.remote_only, notebook_dir);

    sync_notebook_notes(
        token,
        notebook_id,
        notebook_dir,
        options.force,
        &remote_only_action,
        options.keep_orphans,
        options.rename_files,
    )
    .await
}

async fn sync_notebook_notes(
    token: &str,
    notebook_id: &str,
    notebook_dir: &Path,
    force: bool,
    remote_only: &str,
    keep_orphans: bool,
    rename_files: bool,
) -> Result<SyncResult> {
    let notes = notes_service::list_notes(token, notebook_id).await?;

    let mut result = SyncResult::new();

    // Build a map of note IDs to notes for quick lookup
    let note_map: HashMap<String, &Note> =
        notes.iter().map(|n| (n.metadata.id.clone(), n)).collect();

    // Process each remote note
    for note in &notes {
        // Check if note is trashed remotely
        if note.metadata.trashed {
            let is_file_note = note.metadata.note_type == "file";
            let filename = if is_file_note {
                format!("{}.doqium", note.metadata.slug)
            } else {
                format!("{}.md", note.metadata.slug)
            };
            let file_path = notebook_dir.join(&filename);

            if file_path.exists() {
                let should_delete = if force {
                    true
                } else {
                    match common::confirm_action(&format!(
                        "Delete local file '{}' (trashed remotely)?",
                        filename
                    )) {
                        Ok(confirmed) => confirmed,
                        Err(e) => {
                            println!("  ✗ Error getting confirmation: {}", e);
                            result.errors += 1;
                            continue;
                        }
                    }
                };

                if !should_delete {
                    println!("  → Skipping deletion of '{}'", filename);
                    result.skipped += 1;
                    continue;
                }

                println!("  🗑 Deleting '{}': note is trashed remotely", filename);

                // For file notes, delete both the sidecar and binary file
                if is_file_note {
                    if let Some(stem) = file_path.file_stem().and_then(|s| s.to_str()) {
                        if let Ok(entries) = fs::read_dir(notebook_dir) {
                            for entry in entries.flatten() {
                                let binary_path = entry.path();
                                if let Some(binary_stem) =
                                    binary_path.file_stem().and_then(|s| s.to_str())
                                {
                                    if binary_stem == stem {
                                        if let Some(ext) =
                                            binary_path.extension().and_then(|e| e.to_str())
                                        {
                                            if ext != "md" && ext != "doqium" {
                                                if let Err(e) = fs::remove_file(&binary_path) {
                                                    println!("  ⚠ Warning: Could not delete associated file: {}", e);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                if let Err(e) = fs::remove_file(&file_path) {
                    println!("  ✗ Error deleting '{}': {}", filename, e);
                    result.errors += 1;
                } else {
                    println!("  ✓ Successfully deleted '{}'", filename);
                    result.deleted += 1;
                }
            }
            continue;
        }

        // Find local file with this note's ID
        let local_file_with_id = find_local_file_with_id(notebook_dir, &note.metadata.id)?;

        if let Some(file_path) = local_file_with_id {
            // Local file exists with this ID - sync it
            sync_existing_note(
                token,
                notebook_id,
                note,
                &file_path,
                notebook_dir,
                force,
                rename_files,
                &mut result,
            )
            .await?;
        } else {
            // No local file with this ID - this is a remote-only note
            handle_remote_only_note(
                token,
                notebook_id,
                note,
                notebook_dir,
                remote_only,
                force,
                &mut result,
            )
            .await?;
        }
    }

    // Check for orphaned local notes (have ID but not in remote)
    check_orphaned_notes(
        token,
        notebook_id,
        notebook_dir,
        &note_map,
        force,
        keep_orphans,
        &mut result,
    )
    .await?;

    // Check for local PDF files without sidecars and upload them
    let (uploaded, errors) =
        upload_local_pdfs_without_sidecars(token, notebook_id, notebook_dir).await?;
    result.uploaded += uploaded;
    result.errors += errors;

    Ok(result)
}

async fn sync_existing_note(
    token: &str,
    notebook_id: &str,
    note: &Note,
    file_path: &Path,
    notebook_dir: &Path,
    force: bool,
    rename_files: bool,
    result: &mut SyncResult,
) -> Result<()> {
    let filename = file_path
        .file_name()
        .and_then(|s| s.to_str())
        .unwrap_or("unknown");

    // Check if this is a file note (ends with .doqium)
    let is_file_note = file_path.extension().and_then(|s| s.to_str()) == Some("doqium");

    let file_metadata = match fs::metadata(file_path) {
        Ok(m) => m,
        Err(e) => {
            println!("  ✗ Error reading file metadata for '{}': {}", filename, e);
            result.errors += 1;
            return Ok(());
        }
    };

    let file_modified = match file_metadata.modified() {
        Ok(t) => t,
        Err(e) => {
            println!(
                "  ✗ Error getting modification time for '{}': {}",
                filename, e
            );
            result.errors += 1;
            return Ok(());
        }
    };

    let remote_modified = match chrono::DateTime::parse_from_rfc3339(&note.metadata.modified) {
        Ok(dt) => dt,
        Err(e) => {
            println!(
                "  ✗ Error parsing remote modification time for '{}': {}",
                filename, e
            );
            result.errors += 1;
            return Ok(());
        }
    };

    let file_modified_chrono: chrono::DateTime<chrono::Utc> = file_modified.into();

    // Read frontmatter to check if file is actually in sync
    let file_content = match fs::read_to_string(file_path) {
        Ok(c) => c,
        Err(e) => {
            println!("  ✗ Error reading '{}': {}", filename, e);
            result.errors += 1;
            return Ok(());
        }
    };

    let (frontmatter, content) = match common::parse_markdown_frontmatter(&file_content) {
        Ok(parsed) => parsed,
        Err(e) => {
            println!("  ✗ Error parsing frontmatter for '{}': {}", filename, e);
            result.errors += 1;
            return Ok(());
        }
    };

    let frontmatter_modified = frontmatter.get("modified").and_then(|v| v.as_str());

    // For file notes, use .doqium extension
    let expected_filename = if is_file_note || note.metadata.note_type == "file" {
        format!("{}.doqium", note.metadata.slug)
    } else {
        format!("{}.md", note.metadata.slug)
    };
    let actual_file_path = if rename_files && filename != expected_filename {
        let new_path = notebook_dir.join(&expected_filename);

        // Check if target file already exists
        if new_path.exists() {
            println!(
                "  ⚠ Cannot rename '{}' to '{}': target file already exists",
                filename, expected_filename
            );

            let should_overwrite = if force {
                true
            } else {
                match common::confirm_action(&format!(
                    "Overwrite existing file '{}'?",
                    expected_filename
                )) {
                    Ok(confirmed) => confirmed,
                    Err(e) => {
                        println!("  ✗ Error getting confirmation: {}", e);
                        false
                    }
                }
            };

            if !should_overwrite {
                println!("  → Keeping original filename '{}'", filename);
                file_path.to_path_buf()
            } else {
                // Delete existing file first
                if let Err(e) = fs::remove_file(&new_path) {
                    println!(
                        "  ✗ Error removing existing file '{}': {}",
                        expected_filename, e
                    );
                    file_path.to_path_buf()
                } else {
                    match fs::rename(file_path, &new_path) {
                        Ok(_) => {
                            println!("  ✓ Renamed '{}' to '{}'", filename, expected_filename);
                            new_path
                        }
                        Err(e) => {
                            println!(
                                "  ✗ Error renaming '{}' to '{}': {}",
                                filename, expected_filename, e
                            );
                            file_path.to_path_buf()
                        }
                    }
                }
            }
        } else {
            // No conflict, rename directly
            match fs::rename(file_path, &new_path) {
                Ok(_) => {
                    println!("  ✓ Renamed '{}' to '{}'", filename, expected_filename);
                    new_path
                }
                Err(e) => {
                    println!(
                        "  ✗ Error renaming '{}' to '{}': {}",
                        filename, expected_filename, e
                    );
                    file_path.to_path_buf()
                }
            }
        }
    } else {
        file_path.to_path_buf()
    };

    // Check if file is actually in sync (frontmatter matches AND file timestamp is not newer)
    if let Some(fm_modified) = frontmatter_modified {
        if fm_modified == note.metadata.modified && file_modified_chrono <= remote_modified {
            // File is already in sync, skip
            result.skipped += 1;
            return Ok(());
        }
    }

    if file_modified_chrono <= remote_modified {
        // Remote is newer or equal - download
        download_note(
            token,
            notebook_id,
            note,
            &actual_file_path,
            filename,
            result,
        )
        .await
    } else {
        // Local is newer - check for conflicts
        handle_local_newer(
            token,
            notebook_id,
            note,
            &actual_file_path,
            notebook_dir,
            &frontmatter,
            content,
            frontmatter_modified,
            filename,
            result,
        )
        .await
    }
}

async fn download_note(
    token: &str,
    notebook_id: &str,
    note: &Note,
    file_path: &Path,
    filename: &str,
    result: &mut SyncResult,
) -> Result<()> {
    println!("  ↓ Downloading '{}': remote is newer", filename);

    match notes_service::save_note_to_file(token, notebook_id, &note.metadata.id, file_path).await {
        Ok(_) => {
            println!("  ✓ Successfully downloaded '{}'", filename);
            result.downloaded += 1;
        }
        Err(e) => {
            println!("  ✗ Error downloading '{}': {}", filename, e);
            result.errors += 1;
        }
    }
    Ok(())
}

async fn handle_local_newer(
    token: &str,
    notebook_id: &str,
    note: &Note,
    file_path: &Path,
    _notebook_dir: &Path,
    frontmatter: &serde_json::Value,
    content: String,
    frontmatter_modified: Option<&str>,
    filename: &str,
    result: &mut SyncResult,
) -> Result<()> {
    // (frontmatter and content already parsed above)
    let is_conflict = if let Some(fm_modified) = frontmatter_modified {
        fm_modified != note.metadata.modified
    } else {
        // No modified date in frontmatter, assume conflict
        true
    };

    if is_conflict {
        // CONFLICT: Local is newer but modified date doesn't match
        handle_conflict(token, notebook_id, note, file_path, filename, result).await
    } else {
        // No conflict - upload local changes
        upload_note(
            token,
            notebook_id,
            note,
            file_path,
            frontmatter,
            content,
            filename,
            result,
        )
        .await
    }
}

async fn handle_conflict(
    token: &str,
    notebook_id: &str,
    note: &Note,
    file_path: &Path,
    filename: &str,
    result: &mut SyncResult,
) -> Result<()> {
    println!("  ⚠ Conflict detected in '{}'", filename);
    println!("    Local file is newer but frontmatter 'modified' doesn't match remote");
    println!("    Keeping local file, downloading remote as conflict file");

    let conflict_path = create_conflict_filename(file_path, &note.metadata.modified)?;

    match notes_service::save_note_to_file(token, notebook_id, &note.metadata.id, &conflict_path)
        .await
    {
        Ok(_) => {
            println!("  ✓ Successfully created conflict file");
            result.conflicts += 1;
        }
        Err(e) => {
            println!("  ✗ Error creating conflict file: {}", e);
            result.errors += 1;
        }
    }
    Ok(())
}

async fn upload_note(
    token: &str,
    notebook_id: &str,
    note: &Note,
    file_path: &Path,
    frontmatter: &serde_json::Value,
    content: String,
    filename: &str,
    result: &mut SyncResult,
) -> Result<()> {
    println!("  ↑ Uploading '{}': local file is newer", filename);

    match notes_service::build_note_request_from_frontmatter(frontmatter, content, Some(note)) {
        Ok(note_request) => {
            match notes_service::update_note(token, notebook_id, &note.metadata.id, &note_request)
                .await
            {
                Ok(_) => {
                    match notes_service::save_note_to_file(
                        token,
                        notebook_id,
                        &note.metadata.id,
                        file_path,
                    )
                    .await
                    {
                        Ok(_) => {
                            println!("  ✓ Successfully uploaded '{}'", filename);
                            result.uploaded += 1;
                        }
                        Err(e) => {
                            println!("  ✗ Error updating local file '{}': {}", filename, e);
                            result.errors += 1;
                        }
                    }
                }
                Err(e) => {
                    println!("  ✗ Error uploading '{}': {}", filename, e);
                    result.errors += 1;
                }
            }
        }
        Err(e) => {
            println!("  ✗ Error preparing '{}': {}", filename, e);
            result.errors += 1;
        }
    }
    Ok(())
}

async fn handle_remote_only_note(
    token: &str,
    notebook_id: &str,
    note: &Note,
    notebook_dir: &Path,
    remote_only: &str,
    force: bool,
    result: &mut SyncResult,
) -> Result<()> {
    let filename = if note.metadata.note_type == "file" {
        format!("{}.doqium", note.metadata.slug)
    } else {
        format!("{}.md", note.metadata.slug)
    };
    let file_path = notebook_dir.join(&filename);

    match remote_only {
        "download" => {
            println!("  ↓ Downloading remote-only '{}'", filename);

            // Resolve path and check for conflicts
            let (final_path, should_proceed) =
                match resolve_download_path(&file_path, &note.metadata.id, force) {
                    Ok(result) => result,
                    Err(e) => {
                        println!("  ✗ Error resolving download path: {}", e);
                        result.errors += 1;
                        return Ok(());
                    }
                };

            if !should_proceed {
                println!("  → Skipping '{}' (download cancelled)", filename);
                result.skipped += 1;
                return Ok(());
            }

            match notes_service::save_note_to_file(
                token,
                notebook_id,
                &note.metadata.id,
                &final_path,
            )
            .await
            {
                Ok(_) => {
                    let final_filename =
                        final_path.file_name().unwrap_or_default().to_string_lossy();
                    println!("  ✓ Successfully downloaded '{}'", final_filename);
                    result.downloaded += 1;
                }
                Err(e) => {
                    println!("  ✗ Error downloading '{}': {}", filename, e);
                    result.errors += 1;
                }
            }
        }
        "trash" => {
            let should_trash = if force {
                true
            } else {
                match common::confirm_action(&format!(
                    "Trash remote note '{}' (no local file)?",
                    filename
                )) {
                    Ok(confirmed) => confirmed,
                    Err(e) => {
                        println!("  ✗ Error getting confirmation: {}", e);
                        result.errors += 1;
                        return Ok(());
                    }
                }
            };

            if !should_trash {
                println!("  → Skipping trash of '{}'", filename);
                result.skipped += 1;
                return Ok(());
            }

            println!("  🗑 Trashing remote-only '{}'", filename);
            match notes_service::trash_note(token, notebook_id, &note.metadata.id).await {
                Ok(_) => {
                    println!("  ✓ Successfully trashed '{}'", filename);
                    result.deleted += 1;
                }
                Err(e) => {
                    println!("  ✗ Error trashing '{}': {}", filename, e);
                    result.errors += 1;
                }
            }
        }
        "delete" => {
            let should_delete = if force {
                true
            } else {
                match common::confirm_action(&format!(
                    "PERMANENTLY delete remote note '{}' (no local file)?",
                    filename
                )) {
                    Ok(confirmed) => confirmed,
                    Err(e) => {
                        println!("  ✗ Error getting confirmation: {}", e);
                        result.errors += 1;
                        return Ok(());
                    }
                }
            };

            if !should_delete {
                println!("  → Skipping deletion of '{}'", filename);
                result.skipped += 1;
                return Ok(());
            }

            println!("  ✗ Deleting remote-only '{}'", filename);
            match notes_service::delete_note(token, notebook_id, &note.metadata.id).await {
                Ok(_) => {
                    println!("  ✓ Successfully deleted '{}'", filename);
                    result.deleted += 1;
                }
                Err(e) => {
                    println!("  ✗ Error deleting '{}': {}", filename, e);
                    result.errors += 1;
                }
            }
        }
        "ignore" => {
            // Do nothing
        }
        _ => {
            println!("  ⚠ Unknown --remote-only option: {}", remote_only);
        }
    }
    Ok(())
}

async fn check_orphaned_notes(
    _token: &str,
    _notebook_id: &str,
    notebook_dir: &Path,
    note_map: &HashMap<String, &Note>,
    force: bool,
    keep_orphans: bool,
    result: &mut SyncResult,
) -> Result<()> {
    let local_files = match fs::read_dir(notebook_dir) {
        Ok(entries) => entries,
        Err(e) => {
            println!("  ✗ Error reading directory: {}", e);
            result.errors += 1;
            return Ok(());
        }
    };

    for entry in local_files {
        let entry = match entry {
            Ok(e) => e,
            Err(_) => continue,
        };

        let path = entry.path();

        if !path.is_file() {
            continue;
        }

        let ext = path.extension().and_then(|s| s.to_str());
        // Check both .md and .doqium files
        if ext != Some("md") && ext != Some("doqium") {
            continue;
        }

        let file_content = match fs::read_to_string(&path) {
            Ok(c) => c,
            Err(_) => continue,
        };

        let (frontmatter, _) = match common::parse_markdown_frontmatter(&file_content) {
            Ok(parsed) => parsed,
            Err(_) => continue,
        };

        if let Some(file_id) = frontmatter.get("id").and_then(|v| v.as_str()) {
            // Check if this ID exists in remote notes
            if !note_map.contains_key(file_id) {
                // Orphaned note!
                let filename = path
                    .file_name()
                    .and_then(|s| s.to_str())
                    .unwrap_or("unknown");

                if keep_orphans {
                    println!("  ⚠ Keeping orphaned note '{}' (--keep-orphans)", filename);
                    result.skipped += 1;
                    continue;
                }

                let should_delete = if force {
                    true
                } else {
                    common::confirm_action(&format!(
                        "Note '{}' has ID {} but doesn't exist remotely. Delete local file?",
                        filename, file_id
                    ))?
                };

                if should_delete {
                    // For .doqium files, also delete the associated binary file
                    if ext == Some("doqium") {
                        if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
                            // Find and delete the binary file
                            if let Ok(dir_entries) = fs::read_dir(notebook_dir) {
                                for dir_entry in dir_entries.flatten() {
                                    let binary_path = dir_entry.path();
                                    if let Some(binary_stem) =
                                        binary_path.file_stem().and_then(|s| s.to_str())
                                    {
                                        if binary_stem == stem {
                                            if let Some(binary_ext) =
                                                binary_path.extension().and_then(|e| e.to_str())
                                            {
                                                if binary_ext != "md" && binary_ext != "doqium" {
                                                    if let Err(e) = fs::remove_file(&binary_path) {
                                                        println!("  ⚠ Warning: Could not delete associated file: {}", e);
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }

                    match fs::remove_file(&path) {
                        Ok(_) => {
                            println!("  ✓ Deleted orphaned note '{}'", filename);
                            result.deleted += 1;
                        }
                        Err(e) => {
                            println!("  ✗ Error deleting '{}': {}", filename, e);
                            result.errors += 1;
                        }
                    }
                } else {
                    println!("  → Skipping '{}' (deletion cancelled)", filename);
                    result.skipped += 1;
                }
            }
        }
    }

    Ok(())
}

async fn upload_notes_from_folder(
    token: &str,
    notebook_id: &str,
    folder_path: &Path,
) -> Result<(usize, usize)> {
    let mut uploaded = 0;
    let mut errors = 0;

    let entries = fs::read_dir(folder_path)?;
    for entry in entries {
        let entry = entry?;
        let path = entry.path();

        if !path.is_file() {
            continue;
        }

        let filename = path
            .file_name()
            .and_then(|s| s.to_str())
            .ok_or_else(|| anyhow::anyhow!("Invalid filename"))?;

        // Skip metadata and sidecar files
        if filename == "metadata.doqium" || filename.ends_with(".doqium") {
            continue;
        }

        // Handle markdown files
        if filename.ends_with(".md") {
            println!("  ↑ Uploading note: {}", filename);

            let file_content = match fs::read_to_string(&path) {
                Ok(content) => content,
                Err(e) => {
                    println!("  ✗ Error reading '{}': {}", filename, e);
                    errors += 1;
                    continue;
                }
            };

            let (mut frontmatter, content) = match common::parse_markdown_frontmatter(&file_content)
            {
                Ok((fm, c)) => (fm, c),
                Err(e) => {
                    println!("  ✗ Error parsing '{}': {}", filename, e);
                    errors += 1;
                    continue;
                }
            };

            // Ignore ID in frontmatter for new notebooks
            if let Some(obj) = frontmatter.as_object_mut() {
                obj.remove("id");
            }

            match notes_service::build_note_request_from_frontmatter(&frontmatter, content, None) {
                Ok(note_request) => {
                    match notes_service::create_note(token, notebook_id, &note_request).await {
                        Ok(note_id) => {
                            println!("  ✓ Successfully uploaded '{}' (ID: {})", filename, note_id);
                            uploaded += 1;

                            // Update the file with the new ID
                            if let Err(e) = notes_service::save_note_to_file(
                                token,
                                notebook_id,
                                &note_id,
                                &path,
                            )
                            .await
                            {
                                println!("  ⚠ Warning: Could not update file with ID: {}", e);
                            }
                        }
                        Err(e) => {
                            println!("  ✗ Error uploading '{}': {}", filename, e);
                            errors += 1;
                        }
                    }
                }
                Err(e) => {
                    println!("  ✗ Error preparing '{}': {}", filename, e);
                    errors += 1;
                }
            }
        } else {
            // Check if this is a PDF file without a sidecar - create new file note
            let extension = path.extension().and_then(|e| e.to_str());

            // Only process PDF files for now
            if extension == Some("pdf") {
                let stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("file");
                let sidecar_path = folder_path.join(format!("{}.doqium", stem));

                if !sidecar_path.exists() {
                    println!("  ↑ Uploading PDF file as file note: {}", filename);

                    match upload_file_as_note(token, notebook_id, &path, stem).await {
                        Ok(note_id) => {
                            println!(
                                "  ✓ Successfully uploaded PDF '{}' (ID: {})",
                                filename, note_id
                            );
                            uploaded += 1;
                        }
                        Err(e) => {
                            println!("  ✗ Error uploading PDF '{}': {}", filename, e);
                            errors += 1;
                        }
                    }
                }
            }
        }
    }

    Ok((uploaded, errors))
}

async fn upload_local_pdfs_without_sidecars(
    token: &str,
    notebook_id: &str,
    notebook_dir: &Path,
) -> Result<(usize, usize)> {
    let mut uploaded = 0;
    let mut errors = 0;

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

    for entry in entries.flatten() {
        let path = entry.path();

        if !path.is_file() {
            continue;
        }

        // Check if this is a PDF file
        let extension = path.extension().and_then(|e| e.to_str());
        if extension != Some("pdf") {
            continue;
        }

        // Check if it has a sidecar
        let stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("file");
        let sidecar_path = notebook_dir.join(format!("{}.doqium", stem));

        if sidecar_path.exists() {
            continue; // Already has a sidecar, skip
        }

        let filename = path
            .file_name()
            .and_then(|s| s.to_str())
            .unwrap_or("unknown");

        println!("  ↑ Uploading PDF file as file note: {}", filename);

        match upload_file_as_note(token, notebook_id, &path, stem).await {
            Ok(note_id) => {
                println!(
                    "  ✓ Successfully uploaded PDF '{}' (ID: {})",
                    filename, note_id
                );
                uploaded += 1;
            }
            Err(e) => {
                println!("  ✗ Error uploading PDF '{}': {}", filename, e);
                errors += 1;
            }
        }
    }

    Ok((uploaded, errors))
}

async fn upload_file_as_note(
    token: &str,
    notebook_id: &str,
    file_path: &Path,
    title: &str,
) -> Result<String> {
    // Read file data
    let file_data = fs::read(file_path)?;
    let filename = file_path
        .file_name()
        .and_then(|s| s.to_str())
        .ok_or_else(|| anyhow::anyhow!("Invalid filename"))?;

    // Create file note
    let slug = common::sluggify(title);
    let note_request = serde_json::json!({
        "metadata": {
            "type": "file",
            "title": title,
            "slug": slug,
            "labels": [],
            "favorite": false,
            "trashed": false,
            "archived": false,
            "public": false,
        },
        "content": "",
    });

    let note_id = notes_service::create_note(token, notebook_id, &note_request).await?;

    // Upload file as attachment
    let attachment_id = crate::apis::attachments_api::upload_note_attachment(
        token,
        notebook_id,
        &note_id,
        file_data,
        filename,
    )
    .await?;

    // Get attachment details to create proper body
    let attachments =
        crate::apis::attachments_api::list_note_attachments(token, notebook_id, &note_id).await?;

    let attachment = attachments
        .iter()
        .find(|a| a.id.to_string() == attachment_id)
        .ok_or_else(|| anyhow::anyhow!("Attachment not found after upload"))?;

    // Update note body with attachment metadata
    let checksum = attachment
        .checksum
        .as_ref()
        .map(|s| s.as_str())
        .unwrap_or("");
    let body_content = format!(
        "---\nattachmentId: {}\nfileName: {}\ncontentType: {}\nsize: {}\nchecksum: {}\n---\n",
        attachment.id, attachment.file_name, attachment.content_type, attachment.size, checksum
    );

    // Encrypt the body content since the note is private
    let encrypted_content = notes_service::encrypt_note_content(&body_content)?;

    let update_request = serde_json::json!({
        "metadata": {
            "id": note_id,
            "type": "file",
            "title": title,
            "slug": slug,
            "labels": [],
            "favorite": false,
            "trashed": false,
            "archived": false,
            "public": false,
        },
        "content": encrypted_content,
    });

    notes_service::update_note(token, notebook_id, &note_id, &update_request).await?;

    // Download the note back to create the sidecar file
    notes_service::save_note_to_file(
        token,
        notebook_id,
        &note_id,
        &file_path
            .parent()
            .unwrap_or(Path::new("."))
            .join(format!("{}.doqium", slug)),
    )
    .await?;

    Ok(note_id)
}

fn find_local_file_with_id(dir: &Path, note_id: &str) -> Result<Option<PathBuf>> {
    let entries = match fs::read_dir(dir) {
        Ok(e) => e,
        Err(_) => return Ok(None),
    };

    for entry in entries {
        let entry = match entry {
            Ok(e) => e,
            Err(_) => continue,
        };

        let path = entry.path();

        if !path.is_file() {
            continue;
        }

        // Check .md files and .doqium sidecar files
        let ext = path.extension().and_then(|s| s.to_str());
        if ext != Some("md") && ext != Some("doqium") {
            continue;
        }

        let content = match fs::read_to_string(&path) {
            Ok(c) => c,
            Err(_) => continue,
        };

        let (frontmatter, _) = match common::parse_markdown_frontmatter(&content) {
            Ok(parsed) => parsed,
            Err(_) => continue,
        };

        if let Some(id) = frontmatter.get("id").and_then(|v| v.as_str()) {
            if id == note_id {
                return Ok(Some(path));
            }
        }
    }

    Ok(None)
}

fn create_conflict_filename(original_path: &Path, remote_modified: &str) -> Result<PathBuf> {
    let stem = original_path
        .file_stem()
        .and_then(|s| s.to_str())
        .ok_or_else(|| anyhow::anyhow!("Invalid filename"))?;

    let ext = original_path
        .extension()
        .and_then(|s| s.to_str())
        .unwrap_or("md");

    // Parse timestamp and format as ISO8601 compact
    let timestamp = remote_modified
        .chars()
        .filter(|c| c.is_alphanumeric())
        .take(15)
        .collect::<String>();

    let filename = format!("{}.conflict-{}.{}", stem, timestamp, ext);
    Ok(original_path.with_file_name(filename))
}

/// Resolve file path for downloading a note, handling ID conflicts
/// Returns (final_path, should_proceed)
fn resolve_download_path(file_path: &Path, note_id: &str, force: bool) -> Result<(PathBuf, bool)> {
    if !file_path.exists() {
        // No conflict, use original path
        return Ok((file_path.to_path_buf(), true));
    }

    // File exists, check its ID
    let file_content = match fs::read_to_string(file_path) {
        Ok(c) => c,
        Err(e) => {
            println!(
                "  ⚠ Warning: Cannot read existing file '{}': {}",
                file_path.display(),
                e
            );
            // Can't read file, ask user
            if force {
                return Ok((file_path.to_path_buf(), true));
            } else {
                let overwrite = common::confirm_action(&format!(
                    "File '{}' already exists but cannot be read. Overwrite?",
                    file_path.file_name().unwrap_or_default().to_string_lossy()
                ))?;
                return Ok((file_path.to_path_buf(), overwrite));
            }
        }
    };

    let (frontmatter, _) = match common::parse_markdown_frontmatter(&file_content) {
        Ok(parsed) => parsed,
        Err(_) => {
            // Cannot parse frontmatter, no ID present
            println!(
                "  ⚠ Warning: File '{}' has no frontmatter",
                file_path.file_name().unwrap_or_default().to_string_lossy()
            );
            if force {
                return Ok((file_path.to_path_buf(), true));
            } else {
                let overwrite = common::confirm_action(&format!(
                    "File '{}' has no ID. Overwrite?",
                    file_path.file_name().unwrap_or_default().to_string_lossy()
                ))?;
                return Ok((file_path.to_path_buf(), overwrite));
            }
        }
    };

    let existing_id = frontmatter.get("id").and_then(|v| v.as_str());

    match existing_id {
        Some(id) if id == note_id => {
            // Same ID, safe to overwrite
            Ok((file_path.to_path_buf(), true))
        }
        Some(different_id) => {
            // Different ID, create alternate filename with suffix
            println!(
                "  ⚠ File '{}' contains a different note (ID: {})",
                file_path.file_name().unwrap_or_default().to_string_lossy(),
                different_id
            );

            // Find a unique filename by appending a number
            let stem = file_path
                .file_stem()
                .and_then(|s| s.to_str())
                .ok_or_else(|| anyhow::anyhow!("Invalid filename"))?;
            let ext = file_path
                .extension()
                .and_then(|s| s.to_str())
                .unwrap_or("md");
            let parent = file_path
                .parent()
                .ok_or_else(|| anyhow::anyhow!("Invalid path"))?;

            let mut counter = 2;
            let mut new_path = parent.join(format!("{}-{}.{}", stem, counter, ext));
            while new_path.exists() {
                counter += 1;
                new_path = parent.join(format!("{}-{}.{}", stem, counter, ext));
            }

            println!(
                "  → Saving as '{}' to avoid conflict",
                new_path.file_name().unwrap_or_default().to_string_lossy()
            );
            Ok((new_path, true))
        }
        None => {
            // No ID in existing file, prompt user
            println!(
                "  ⚠ File '{}' has no ID in frontmatter",
                file_path.file_name().unwrap_or_default().to_string_lossy()
            );
            if force {
                return Ok((file_path.to_path_buf(), true));
            } else {
                let overwrite = common::confirm_action(&format!(
                    "Overwrite file '{}' (has no ID)?",
                    file_path.file_name().unwrap_or_default().to_string_lossy()
                ))?;
                return Ok((file_path.to_path_buf(), overwrite));
            }
        }
    }
}

fn determine_remote_only_action(remote_only: &Option<String>, _sync_dir: &Path) -> String {
    if let Some(action) = remote_only {
        return action.clone();
    }

    // Default to download for all syncs
    "download".to_string()
}

fn create_workspace_metadata(workspace_dir: &Path, id: &str, name: &str, slug: &str) -> Result<()> {
    let metadata_path = workspace_dir.join("metadata.doqium");
    let metadata_content = serde_json::json!({
        "id": id,
        "name": name,
        "slug": slug,
    });
    fs::write(
        &metadata_path,
        serde_json::to_string_pretty(&metadata_content)?,
    )?;
    Ok(())
}

fn create_notebook_metadata(notebook_dir: &Path, id: &str, name: &str, slug: &str) -> Result<()> {
    let metadata_path = notebook_dir.join("metadata.doqium");
    let metadata_content = serde_json::json!({
        "id": id,
        "name": name,
        "slug": slug,
    });
    fs::write(
        &metadata_path,
        serde_json::to_string_pretty(&metadata_content)?,
    )?;
    Ok(())
}
