use crate::selector::NoteSelector;
use crate::services::{common, notebooks_service, notes_service, workspaces_service};
use anyhow::Result;
use clap::Subcommand;
use std::fs;

#[derive(Subcommand)]
pub enum NotesCommands {
    /// List all notes (optionally filtered by workspace or notebook)
    #[clap(visible_alias("ls"))]
    List {
        /// Filter by workspace or notebook selector (@notebook[workspace/notebook], workspace slug, or notebook ID)
        #[arg(value_name = "SELECTOR")]
        selector: Option<String>,
    },
    /// Save a note to a file
    Save {
        /// Doqium selector (@notebook[workspace/notebook]/note) or note ID
        #[arg(value_name = "SELECTOR")]
        selector: String,
        /// Notebook ID (required if using note ID instead of selector)
        #[arg(long = "notebook-id")]
        notebook_id: Option<String>,
        /// Output file path (defaults to workspace-notebook-note.md)
        #[arg(long = "output", short = 'o')]
        output: Option<String>,
    },
    /// Update an existing note from a file
    Update {
        /// Doqium selector (@notebook[workspace/notebook]/note) or note ID
        #[arg(value_name = "SELECTOR")]
        selector: String,
        /// Input file path (defaults to workspace-notebook-note.md)
        #[arg(long = "input", short = 'i')]
        input: Option<String>,
        /// Notebook ID (required if using note ID instead of selector)
        #[arg(long = "notebook-id")]
        notebook_id: Option<String>,
    },
    /// Create a new note from a file
    Create {
        /// Input file path
        #[arg(long = "input", short = 'i')]
        input: String,
        /// Doqium selector for target notebook (@notebook[workspace/notebook]) or notebook ID
        #[arg(long = "notebook", short = 'n')]
        notebook: String,
    },
    /// Trash a note (marks it as trashed)
    Trash {
        /// Doqium selector (@notebook[workspace/notebook]/note) or note ID
        #[arg(value_name = "SELECTOR")]
        selector: String,
        /// Notebook ID (required if using note ID instead of selector)
        #[arg(long = "notebook-id")]
        notebook_id: Option<String>,
    },
    /// Untrash a note (restores from trash)
    Untrash {
        /// Doqium selector (@notebook[workspace/notebook]/note) or note ID
        #[arg(value_name = "SELECTOR")]
        selector: String,
        /// Notebook ID (required if using note ID instead of selector)
        #[arg(long = "notebook-id")]
        notebook_id: Option<String>,
    },
    /// Publish a note (makes it public)
    Publish {
        /// Doqium selector (@notebook[workspace/notebook]/note) or note ID
        #[arg(value_name = "SELECTOR")]
        selector: String,
        /// Notebook ID (required if using note ID instead of selector)
        #[arg(long = "notebook-id")]
        notebook_id: Option<String>,
    },
    /// Unpublish a note (makes it private)
    Unpublish {
        /// Doqium selector (@notebook[workspace/notebook]/note) or note ID
        #[arg(value_name = "SELECTOR")]
        selector: String,
        /// Notebook ID (required if using note ID instead of selector)
        #[arg(long = "notebook-id")]
        notebook_id: Option<String>,
    },
    /// Favorite a note (marks it as favorite)
    Favorite {
        /// Doqium selector (@notebook[workspace/notebook]/note) or note ID
        #[arg(value_name = "SELECTOR")]
        selector: String,
        /// Notebook ID (required if using note ID instead of selector)
        #[arg(long = "notebook-id")]
        notebook_id: Option<String>,
    },
    /// Unfavorite a note (removes from favorites)
    Unfavorite {
        /// Doqium selector (@notebook[workspace/notebook]/note) or note ID
        #[arg(value_name = "SELECTOR")]
        selector: String,
        /// Notebook ID (required if using note ID instead of selector)
        #[arg(long = "notebook-id")]
        notebook_id: Option<String>,
    },
    /// Delete a note permanently
    Delete {
        /// Doqium selector (@notebook[workspace/notebook]/note) or note ID
        #[arg(value_name = "SELECTOR")]
        selector: String,
        /// Notebook ID (required if using note ID instead of selector)
        #[arg(long = "notebook-id")]
        notebook_id: Option<String>,
    },
}

async fn resolve_note_ids(
    token: &str,
    selector: &str,
    notebook_id: Option<String>,
) -> Result<(String, String)> {
    let parsed_selector = NoteSelector::parse(selector, notebook_id)?;

    if parsed_selector.needs_resolution() {
        notes_service::resolve_note_selector(token, &parsed_selector, None).await
    } else {
        let note_id = parsed_selector
            .note_id
            .ok_or_else(|| anyhow::anyhow!("Note ID is required"))?;
        let notebook_id = parsed_selector
            .notebook_id
            .ok_or_else(|| anyhow::anyhow!("Notebook ID is required when using note ID"))?;
        Ok((note_id, notebook_id))
    }
}

pub async fn handle_list(token: &str, selector: &Option<String>) -> Result<()> {
    let enriched_notes = notes_service::list_enriched_notes(token).await?;

    let filtered_notes: Vec<_> = if let Some(sel) = selector {
        if sel.starts_with("@notebook[") {
            let parsed = crate::selector::parse_notebook_selector(sel)?;
            let (workspace_slug, notebook_slug, notebook_id) = parsed;

            if let Some(nb_id) = notebook_id {
                enriched_notes
                    .into_iter()
                    .filter(|item| item.note.metadata.id.starts_with(&nb_id))
                    .collect()
            } else {
                enriched_notes
                    .into_iter()
                    .filter(|item| {
                        let ws_match = workspace_slug
                            .as_ref()
                            .map_or(true, |ws| &item.workspace_slug == ws);
                        let nb_match = notebook_slug
                            .as_ref()
                            .map_or(true, |nb| &item.notebook_slug == nb);
                        ws_match && nb_match
                    })
                    .collect()
            }
        } else if sel.len() == 36 && sel.contains('-') {
            enriched_notes
                .into_iter()
                .filter(|item| {
                    item.note.metadata.id.starts_with(sel)
                        || item.notebook_slug == *sel
                        || item.workspace_slug == *sel
                })
                .collect()
        } else {
            enriched_notes
                .into_iter()
                .filter(|item| item.workspace_slug == *sel || item.notebook_slug == *sel)
                .collect()
        }
    } else {
        enriched_notes
    };

    if filtered_notes.is_empty() {
        if selector.is_some() {
            println!("No notes found matching the specified selector.");
        } else {
            println!("No notes found.");
        }
    } else {
        println!("Notes:");
        println!(
            "{:<70} {:<20} {:<20} {:<25} {:<30} {}",
            "Selector", "Workspace", "Notebook", "Slug", "Title", "ID"
        );
        println!("{}", "-".repeat(200));
        for item in filtered_notes {
            let selector = format!(
                "@notebook[{}/{}]/{}",
                item.workspace_slug, item.notebook_slug, item.note.metadata.slug
            );
            println!(
                "{:<70} {:<20} {:<20} {:<25} {:<30} {}",
                selector,
                item.workspace_name,
                item.notebook_name,
                item.note.metadata.slug,
                item.note.metadata.title,
                item.note.metadata.id
            );
        }
    }

    Ok(())
}

pub async fn handle_save(
    token: &str,
    selector: &str,
    notebook_id: Option<String>,
    output: &Option<String>,
) -> Result<()> {
    let (resolved_note_id, resolved_notebook_id) =
        resolve_note_ids(token, selector, notebook_id).await?;

    let workspaces = workspaces_service::list_workspaces(token).await?;
    let notebooks = notebooks_service::list_notebooks(token).await?;

    let notebook = notebooks
        .iter()
        .find(|n| n.id == resolved_notebook_id)
        .ok_or_else(|| anyhow::anyhow!("Notebook not found"))?;

    let workspace = workspaces
        .iter()
        .find(|w| w.id == notebook.workspace_id)
        .ok_or_else(|| anyhow::anyhow!("Workspace not found"))?;

    let note = notes_service::get_note(token, &resolved_notebook_id, &resolved_note_id).await?;

    let output_path = if let Some(out) = output {
        out.clone()
    } else {
        format!(
            "{}-{}-{}.md",
            workspace.slug, notebook.slug, note.metadata.slug
        )
    };

    notes_service::save_note_to_file(
        token,
        &resolved_notebook_id,
        &resolved_note_id,
        &std::path::Path::new(&output_path),
    )
    .await?;

    println!("✓ Note saved to: {}", output_path);

    Ok(())
}

pub async fn handle_update(
    token: &str,
    selector: &str,
    input: &Option<String>,
    notebook_id: Option<String>,
) -> Result<()> {
    let parsed_selector = NoteSelector::parse(selector, notebook_id.clone())?;

    let input_path = if let Some(ref inp) = input {
        Some(inp.clone())
    } else if parsed_selector.needs_resolution() {
        let (_, notebook_id_resolved) = if parsed_selector.notebook_id.is_some() {
            (String::new(), parsed_selector.notebook_id.clone().unwrap())
        } else {
            let workspaces = workspaces_service::list_workspaces(token).await?;
            let notebooks = notebooks_service::list_notebooks(token).await?;

            let notebook = if let Some(workspace_slug) = &parsed_selector.workspace_slug {
                let workspace = workspaces
                    .iter()
                    .find(|w| w.slug == *workspace_slug)
                    .ok_or_else(|| anyhow::anyhow!("Workspace '{}' not found", workspace_slug))?;

                notebooks
                    .iter()
                    .find(|n| {
                        n.workspace_id == workspace.id
                            && parsed_selector
                                .notebook_slug
                                .as_ref()
                                .map_or(false, |slug| n.slug == *slug)
                    })
                    .ok_or_else(|| {
                        anyhow::anyhow!(
                            "Notebook '{}' not found in workspace '{}'",
                            parsed_selector.notebook_slug.as_ref().unwrap(),
                            workspace_slug
                        )
                    })?
            } else if let Some(notebook_slug) = &parsed_selector.notebook_slug {
                notebooks
                    .iter()
                    .find(|n| n.slug == *notebook_slug)
                    .ok_or_else(|| anyhow::anyhow!("Notebook '{}' not found", notebook_slug))?
            } else {
                anyhow::bail!("No notebook specified in selector");
            };

            let workspace = workspaces
                .iter()
                .find(|w| w.id == notebook.workspace_id)
                .ok_or_else(|| anyhow::anyhow!("Workspace not found"))?;

            (workspace.slug.clone(), notebook.id.clone())
        };

        let workspaces = workspaces_service::list_workspaces(token).await?;
        let notebooks = notebooks_service::list_notebooks(token).await?;

        let notebook = notebooks
            .iter()
            .find(|n| n.id == notebook_id_resolved)
            .ok_or_else(|| anyhow::anyhow!("Notebook not found"))?;

        let workspace = workspaces
            .iter()
            .find(|w| w.id == notebook.workspace_id)
            .ok_or_else(|| anyhow::anyhow!("Workspace not found"))?;

        if let Some(ref note_slug) = parsed_selector.note_slug {
            Some(format!(
                "{}-{}-{}.md",
                workspace.slug, notebook.slug, note_slug
            ))
        } else {
            None
        }
    } else {
        None
    };

    let (file_note_id, file_notebook_id) = if let Some(ref input_path) = input_path {
        if let Ok(file_content) = fs::read_to_string(input_path) {
            crate::utils::debug_file_content(input_path, &file_content);

            if let Ok((frontmatter, _)) = common::parse_markdown_frontmatter(&file_content) {
                let id = frontmatter
                    .get("id")
                    .and_then(|v| v.as_str())
                    .map(|s| s.to_string());

                if let Some(ref note_id) = id {
                    let nb_id = if let Some(nb_id) = parsed_selector.notebook_id.clone() {
                        Some(nb_id)
                    } else if parsed_selector.needs_resolution() {
                        let (_, nb_id) =
                            resolve_note_ids(token, selector, notebook_id.clone()).await?;
                        Some(nb_id)
                    } else {
                        None
                    };

                    if let Some(nb_id) = nb_id {
                        println!("Using note ID from file: {}", note_id);
                        (Some(note_id.clone()), Some(nb_id))
                    } else {
                        (id, None)
                    }
                } else {
                    (None, None)
                }
            } else {
                (None, None)
            }
        } else {
            (None, None)
        }
    } else {
        (None, None)
    };

    let (resolved_note_id, resolved_notebook_id) =
        if let (Some(note_id), Some(notebook_id)) = (file_note_id.clone(), file_notebook_id) {
            (note_id, notebook_id)
        } else if parsed_selector.needs_resolution() {
            notes_service::resolve_note_selector(token, &parsed_selector, file_note_id.as_deref())
                .await?
        } else {
            let note_id = parsed_selector
                .note_id
                .ok_or_else(|| anyhow::anyhow!("Note ID is required"))?;
            let notebook_id = parsed_selector
                .notebook_id
                .ok_or_else(|| anyhow::anyhow!("Notebook ID is required when using note ID"))?;
            (note_id, notebook_id)
        };

    let workspaces = workspaces_service::list_workspaces(token).await?;
    let notebooks = notebooks_service::list_notebooks(token).await?;

    let notebook = notebooks
        .iter()
        .find(|n| n.id == resolved_notebook_id)
        .ok_or_else(|| anyhow::anyhow!("Notebook not found"))?;

    let workspace = workspaces
        .iter()
        .find(|w| w.id == notebook.workspace_id)
        .ok_or_else(|| anyhow::anyhow!("Workspace not found"))?;

    let existing_note =
        notes_service::get_note(token, &resolved_notebook_id, &resolved_note_id).await;

    let input_path = if let Some(inp) = input {
        inp.clone()
    } else {
        let note_slug = if let Ok(note) = &existing_note {
            note.metadata.slug.clone()
        } else {
            parsed_selector
                .note_slug
                .clone()
                .ok_or_else(|| anyhow::anyhow!("Cannot determine note slug for default filename"))?
        };
        format!("{}-{}-{}.md", workspace.slug, notebook.slug, note_slug)
    };

    println!("Reading from: {}", input_path);

    let file_content = fs::read_to_string(&input_path)?;
    let (frontmatter, content) = common::parse_markdown_frontmatter(&file_content)?;

    if let Some(file_note_id) = frontmatter.get("id").and_then(|v| v.as_str()) {
        if file_note_id != resolved_note_id {
            anyhow::bail!(
                "Note ID mismatch: selector resolved to '{}' but file contains ID '{}'",
                resolved_note_id,
                file_note_id
            );
        }
    }

    if existing_note.is_err() {
        anyhow::bail!(
            "Note does not exist. Use 'notes create' to create a new note: {}",
            resolved_note_id
        );
    }

    let existing_note = existing_note.unwrap();
    let note_request = notes_service::build_note_request_from_frontmatter(
        &frontmatter,
        content,
        Some(&existing_note),
    )?;

    println!("Updating existing note: {}", resolved_note_id);
    notes_service::update_note(
        token,
        &resolved_notebook_id,
        &resolved_note_id,
        &note_request,
    )
    .await?;
    println!("✓ Note updated successfully");

    Ok(())
}

pub async fn handle_create(token: &str, input: &str, notebook: &str) -> Result<()> {
    let (workspace_slug, notebook_slug, notebook_id) =
        crate::selector::parse_notebook_selector(notebook)?;

    let target_notebook_id = if let Some(nb_id) = notebook_id {
        nb_id
    } else {
        let workspaces = workspaces_service::list_workspaces(token).await?;
        let notebooks = notebooks_service::list_notebooks(token).await?;

        let matching_notebooks: Vec<_> = notebooks
            .iter()
            .filter(|nb| {
                if let Some(ref slug) = notebook_slug {
                    if &nb.slug != slug {
                        return false;
                    }
                }

                if let Some(ref ws_slug) = workspace_slug {
                    if let Some(workspace) = workspaces.iter().find(|w| w.id == nb.workspace_id) {
                        if &workspace.slug != ws_slug {
                            return false;
                        }
                    } else {
                        return false;
                    }
                }

                true
            })
            .collect();

        if matching_notebooks.is_empty() {
            anyhow::bail!("No notebook found matching selector: {}", notebook);
        } else if matching_notebooks.len() > 1 {
            anyhow::bail!(
                "Multiple notebooks found matching selector: {}. Please specify workspace.",
                notebook
            );
        }

        matching_notebooks[0].id.clone()
    };

    println!("Reading from: {}", input);

    let file_content = fs::read_to_string(input)?;
    let (frontmatter, content) = common::parse_markdown_frontmatter(&file_content)?;

    if frontmatter.get("id").is_some() {
        anyhow::bail!(
            "Cannot create note: 'id' field found in frontmatter. Remove the 'id' field or use 'notes update' instead."
        );
    }

    let note_request =
        notes_service::build_note_request_from_frontmatter(&frontmatter, content, None)?;

    println!("Creating new note...");
    let note_id = notes_service::create_note(token, &target_notebook_id, &note_request).await?;

    let slug = note_request
        .get("metadata")
        .and_then(|m| m.get("slug"))
        .and_then(|s| s.as_str())
        .unwrap_or("unknown");

    println!("✓ Note created successfully");
    println!("  Note ID: {}", note_id);
    println!("  Slug: {}", slug);

    Ok(())
}

pub async fn handle_trash(token: &str, selector: &str, notebook_id: Option<String>) -> Result<()> {
    let (resolved_note_id, resolved_notebook_id) =
        resolve_note_ids(token, selector, notebook_id).await?;

    let note = notes_service::get_note(token, &resolved_notebook_id, &resolved_note_id).await?;

    if note.metadata.trashed {
        println!("Note is already trashed.");
        return Ok(());
    }

    notes_service::update_note_property(
        token,
        &resolved_notebook_id,
        &resolved_note_id,
        "trashed",
        serde_json::Value::Bool(true),
    )
    .await?;
    println!("✓ Note trashed successfully");

    Ok(())
}

pub async fn handle_untrash(
    token: &str,
    selector: &str,
    notebook_id: Option<String>,
) -> Result<()> {
    let (resolved_note_id, resolved_notebook_id) =
        resolve_note_ids(token, selector, notebook_id).await?;

    let note = notes_service::get_note(token, &resolved_notebook_id, &resolved_note_id).await?;

    if !note.metadata.trashed {
        println!("Note is not trashed.");
        return Ok(());
    }

    notes_service::update_note_property(
        token,
        &resolved_notebook_id,
        &resolved_note_id,
        "trashed",
        serde_json::Value::Bool(false),
    )
    .await?;
    println!("✓ Note restored from trash successfully");

    Ok(())
}

pub async fn handle_publish(
    token: &str,
    selector: &str,
    notebook_id: Option<String>,
) -> Result<()> {
    let (resolved_note_id, resolved_notebook_id) =
        resolve_note_ids(token, selector, notebook_id).await?;

    let note = notes_service::get_note(token, &resolved_notebook_id, &resolved_note_id).await?;

    if note.metadata.public {
        println!("Note is already public.");
        return Ok(());
    }

    notes_service::update_note_property(
        token,
        &resolved_notebook_id,
        &resolved_note_id,
        "public",
        serde_json::Value::Bool(true),
    )
    .await?;
    println!("✓ Note published successfully (now public and unencrypted)");

    Ok(())
}

pub async fn handle_unpublish(
    token: &str,
    selector: &str,
    notebook_id: Option<String>,
) -> Result<()> {
    let (resolved_note_id, resolved_notebook_id) =
        resolve_note_ids(token, selector, notebook_id).await?;

    let note = notes_service::get_note(token, &resolved_notebook_id, &resolved_note_id).await?;

    if !note.metadata.public {
        println!("Note is already private.");
        return Ok(());
    }

    notes_service::update_note_property(
        token,
        &resolved_notebook_id,
        &resolved_note_id,
        "public",
        serde_json::Value::Bool(false),
    )
    .await?;
    println!("✓ Note unpublished successfully (now private and encrypted)");

    Ok(())
}

pub async fn handle_favorite(
    token: &str,
    selector: &str,
    notebook_id: Option<String>,
) -> Result<()> {
    let (resolved_note_id, resolved_notebook_id) =
        resolve_note_ids(token, selector, notebook_id).await?;

    let note = notes_service::get_note(token, &resolved_notebook_id, &resolved_note_id).await?;

    if note.metadata.favorite {
        println!("Note is already favorited.");
        return Ok(());
    }

    notes_service::update_note_property(
        token,
        &resolved_notebook_id,
        &resolved_note_id,
        "favorite",
        serde_json::Value::Bool(true),
    )
    .await?;
    println!("✓ Note favorited successfully");

    Ok(())
}

pub async fn handle_unfavorite(
    token: &str,
    selector: &str,
    notebook_id: Option<String>,
) -> Result<()> {
    let (resolved_note_id, resolved_notebook_id) =
        resolve_note_ids(token, selector, notebook_id).await?;

    let note = notes_service::get_note(token, &resolved_notebook_id, &resolved_note_id).await?;

    if !note.metadata.favorite {
        println!("Note is not favorited.");
        return Ok(());
    }

    notes_service::update_note_property(
        token,
        &resolved_notebook_id,
        &resolved_note_id,
        "favorite",
        serde_json::Value::Bool(false),
    )
    .await?;
    println!("✓ Note unfavorited successfully");

    Ok(())
}

pub async fn handle_delete(token: &str, selector: &str, notebook_id: Option<String>) -> Result<()> {
    let (resolved_note_id, resolved_notebook_id) =
        resolve_note_ids(token, selector, notebook_id).await?;

    let note = notes_service::get_note(token, &resolved_notebook_id, &resolved_note_id).await?;

    println!("\nWarning: You are about to permanently delete this note:");
    println!("  Title: {}", note.metadata.title);
    println!("  ID: {}", note.metadata.id);
    println!("  Slug: {}", note.metadata.slug);
    println!("\nThis action cannot be undone!");

    if !common::confirm_action("Are you sure you want to delete this note?")? {
        println!("Deletion cancelled.");
        return Ok(());
    }

    notes_service::delete_note(token, &resolved_notebook_id, &resolved_note_id).await?;
    println!("✓ Note deleted permanently");

    Ok(())
}
