use anyhow::Result;
use colored::Colorize;
use comfy_table::presets::UTF8_FULL;
use comfy_table::{Cell, ContentArrangement, Table};

use crate::api::{CreateLabelParams, PlaneApiClient, PlaneLabel};
use crate::config::{ProjectScope, resolve_plane_session};
use crate::terminal_ui::OutputFormat;

/// Dispatches label CRUD subcommands (ls, create, delete) for the active project.
/// Delegates to the matched `LabelCommandAction` variant after clap parses the subcommand tree.
#[derive(clap::Args)]
pub struct LabelCommandArgs {
    #[command(subcommand)]
    action: LabelCommandAction,
}

/// Discriminated union of label operations that clap routes user input into.
/// Each variant carries the parsed arguments for its specific operation.
#[derive(clap::Subcommand)]
pub enum LabelCommandAction {
    /// List all labels in the project with name and color
    Ls(LabelCommandListArgs),
    /// Create a new label with a display name and hex color code
    Create(LabelCommandCreateArgs),
    /// Delete a label by case-insensitive name match
    Delete(LabelCommandDeleteArgs),
}

/// Fetches and renders all labels defined in the target project.
/// Supports table, JSON, and CSV output formats via --format, and
/// accepts --project to override the configured default project slug.
/// Empty strings for project and format trigger default behavior.
#[derive(clap::Args)]
pub struct LabelCommandListArgs {
    /// Project slug to query (overrides the configured default project)
    #[arg(long, default_value = "")]
    project: String,
    /// Output format: table (default for TTY), json (default for pipes), csv
    #[arg(long, default_value = "")]
    format: String,
}

/// Creates a new label in the target project by POSTing the name and hex color
/// to the Plane labels endpoint. Prints the created label name on success.
#[derive(clap::Args)]
pub struct LabelCommandCreateArgs {
    /// Display name for the new label (e.g. "Bug", "Feature", "Chore")
    name: String,
    /// Hex color code for the label (e.g. "#ff0000")
    #[arg(long, default_value = "#3a3a3a")]
    color: String,
    /// Project slug to target (overrides the configured default project)
    #[arg(long, default_value = "")]
    project: String,
}

/// Resolves a label by case-insensitive name match and deletes it from the project.
/// Bails with the list of available label names when the target is not found.
#[derive(clap::Args)]
pub struct LabelCommandDeleteArgs {
    /// Name of the label to delete (case-insensitive match)
    name: String,
    /// Project slug to target (overrides the configured default project)
    #[arg(long, default_value = "")]
    project: String,
}

/// Renders label collections in table, JSON, and CSV formats for terminal output
struct LabelCommandRenderer<'a> {
    /// The label collection to render
    project_labels: &'a [PlaneLabel],
}

/// Subcommand dispatch for the label command tree
impl LabelCommandArgs {
    /// Routes to the matched label subcommand handler after clap parsing
    pub async fn execute(self) -> Result<()> {
        match self.action {
            LabelCommandAction::Ls(list_labels) => list_labels.execute().await,
            LabelCommandAction::Create(create_label) => create_label.execute().await,
            LabelCommandAction::Delete(delete_label) => delete_label.execute().await,
        }
    }
}

/// Resolves a project slug string: uses the explicit value when non-empty,
/// falls back to the configured default when empty
fn resolve_label_command_project(
    project_text: &str,
    api_client: &PlaneApiClient,
) -> Result<crate::config::ProjectSlug> {
    match project_text {
        "" => match api_client.project_scope() {
            ProjectScope::Scoped(default_project) => default_project.as_str().parse(),
            ProjectScope::ExplicitOnly => {
                anyhow::bail!(
                    "no default project configured — pass --project or run: \
                     plane config --project <slug>"
                );
            }
        },
        explicit => explicit.parse(),
    }
}

/// Resolves a format string: uses the explicit value when non-empty,
/// falls back to TTY auto-detection when empty
fn resolve_label_command_format(format_text: &str) -> Result<OutputFormat> {
    match format_text {
        "" => Ok(OutputFormat::detect_from_terminal()),
        explicit => explicit.parse(),
    }
}

/// Table, JSON, and CSV rendering for label collections
impl<'a> LabelCommandRenderer<'a> {
    /// Renders labels as a colored terminal table with name and color columns
    fn print_table(&self) {
        if self.project_labels.is_empty() {
            println!("No labels found.");
            return;
        }

        let mut terminal_table = Table::new();
        terminal_table
            .load_preset(UTF8_FULL)
            .set_content_arrangement(ContentArrangement::Dynamic)
            .set_header(vec![Cell::new("Label"), Cell::new("Color")]);

        for label_entry in self.project_labels {
            terminal_table.add_row(vec![
                Cell::new(label_entry.display_name().bold().to_string()),
                Cell::new(label_entry.color_hex().dimmed().to_string()),
            ]);
        }

        println!("{}", terminal_table);
    }

    /// Renders labels as a JSON array with id, name, and color fields
    fn print_json(&self) {
        let json_entries: Vec<serde_json::Value> = self
            .project_labels
            .iter()
            .map(|label_entry| {
                serde_json::json!({
                    "id": label_entry.id().to_string(),
                    "name": label_entry.display_name(),
                    "color": label_entry.color_hex(),
                })
            })
            .collect();

        match serde_json::to_string_pretty(&json_entries) {
            Ok(formatted_json) => println!("{}", formatted_json),
            Err(serialization_error) => {
                eprintln!("error serializing JSON: {}", serialization_error);
            }
        }
    }

    /// Renders labels as CSV with id, name, and color columns
    fn print_csv(&self) {
        println!("id,name,color");
        for label_entry in self.project_labels {
            println!(
                "{},\"{}\",{}",
                label_entry.id().to_string(),
                label_entry.display_name().replace('"', "\"\""),
                label_entry.color_hex(),
            );
        }
    }
}

/// Fetches and renders project labels in the selected output format
impl LabelCommandListArgs {
    /// Resolves the project, fetches labels, and renders them as table/JSON/CSV
    pub async fn execute(self) -> Result<()> {
        let plane_session = resolve_plane_session()?;
        let api_client = PlaneApiClient::try_new(plane_session)?;

        let target_project_slug =
            resolve_label_command_project(&self.project, &api_client)?;
        let resolved_project = api_client
            .find_project_by_slug(&target_project_slug)
            .await?;

        let project_client = api_client.project_operations(resolved_project.id());
        let project_labels = project_client.list_labels().await?;

        let selected_format = resolve_label_command_format(&self.format)?;

        let renderer = LabelCommandRenderer {
            project_labels: &project_labels,
        };

        match selected_format {
            OutputFormat::Table => renderer.print_table(),
            OutputFormat::Json => renderer.print_json(),
            OutputFormat::Csv => renderer.print_csv(),
        }

        Ok(())
    }
}

/// POSTs a new label to the Plane API and confirms the result
impl LabelCommandCreateArgs {
    /// Creates the label in the resolved project and prints confirmation
    pub async fn execute(self) -> Result<()> {
        let plane_session = resolve_plane_session()?;
        let api_client = PlaneApiClient::try_new(plane_session)?;

        let target_project_slug =
            resolve_label_command_project(&self.project, &api_client)?;
        let resolved_project = api_client
            .find_project_by_slug(&target_project_slug)
            .await?;

        let project_client = api_client.project_operations(resolved_project.id());

        let create_params = CreateLabelParams {
            label_name: &self.name,
            color_hex: &self.color,
        };
        let created_label = project_client.create_label(create_params).await?;

        println!(
            "Created label {} {}",
            created_label.display_name().bold(),
            created_label.color_hex().dimmed()
        );

        Ok(())
    }
}

/// Resolves a label by name and DELETEs it from the project
impl LabelCommandDeleteArgs {
    /// Finds the label by case-insensitive name match and deletes it
    pub async fn execute(self) -> Result<()> {
        let plane_session = resolve_plane_session()?;
        let api_client = PlaneApiClient::try_new(plane_session)?;

        let target_project_slug =
            resolve_label_command_project(&self.project, &api_client)?;
        let resolved_project = api_client
            .find_project_by_slug(&target_project_slug)
            .await?;

        let project_client = api_client.project_operations(resolved_project.id());
        let project_labels = project_client.list_labels().await?;

        let mut matched_label: Option<&PlaneLabel> = Option::None;
        for candidate_label in &project_labels {
            if candidate_label
                .display_name()
                .eq_ignore_ascii_case(&self.name)
            {
                matched_label = Option::Some(candidate_label);
            }
        }

        let resolved_label = match matched_label {
            Option::Some(found_label) => found_label,
            Option::None => {
                let available: Vec<&str> = project_labels
                    .iter()
                    .map(|label_entry| label_entry.display_name())
                    .collect();
                anyhow::bail!(
                    "label '{}' not found — available: {}",
                    self.name,
                    available.join(", ")
                );
            }
        };

        let label_display_name = resolved_label.display_name().to_owned();
        project_client.delete_label(resolved_label.id()).await?;

        println!("Deleted label {}", label_display_name.bold());

        Ok(())
    }
}
