use anyhow::Result;

use crate::api::{PlaneApiClient, WorkItemIdentifier};
use crate::config::{ProjectScope, resolve_plane_session};

/// Destination for a work item move operation.
/// Exactly one of --cycle or --module must be specified.
enum MoveDestination {
    /// Move the work item into the named cycle
    ToCycle(String),
    /// Move the work item into the named module
    ToModule(String),
}

/// Moves a work item into a cycle or module within the active project.
/// Adds the work item to the target cycle or module. If the item is already
/// in the cycle or module, Plane treats it as a no-op.
#[derive(clap::Args)]
pub struct MoveWorkItemArgs {
    /// Work item identifier to move (e.g. "NRO-123")
    identifier: String,
    /// Target cycle name to add the work item to
    #[arg(long)]
    cycle: Option<String>,
    /// Target module name to add the work item to
    #[arg(long)]
    module: Option<String>,
}

/// Execution logic for the `plane mv` subcommand
impl MoveWorkItemArgs {
    /// Resolves the work item and target cycle or module, then adds
    /// the work item to the target via the Plane API
    pub async fn execute(self) -> Result<()> {
        let move_destination = match (self.cycle, self.module) {
            (Option::Some(cycle_name), Option::None) => MoveDestination::ToCycle(cycle_name),
            (Option::None, Option::Some(module_name)) => MoveDestination::ToModule(module_name),
            (Option::Some(_), Option::Some(_)) => {
                anyhow::bail!("specify either --cycle or --module, not both");
            }
            (Option::None, Option::None) => {
                anyhow::bail!("specify a destination: --cycle <name> or --module <name>");
            }
        };

        let parsed_identifier: WorkItemIdentifier = self.identifier.parse()?;

        let plane_session = resolve_plane_session()?;
        let api_client = PlaneApiClient::try_new(plane_session)?;

        let fetched_work_item = api_client
            .find_work_item_by_identifier(&parsed_identifier)
            .await?;

        let target_project_slug = match api_client.project_scope() {
            ProjectScope::Scoped(default_project) => default_project.as_str().parse()?,
            ProjectScope::ExplicitOnly => {
                anyhow::bail!(
                    "no default project configured — run: plane config --project <slug>"
                );
            }
        };

        let resolved_project = api_client
            .find_project_by_slug(&target_project_slug)
            .await?;
        let project_client = api_client.project_operations(resolved_project.id());

        match move_destination {
            MoveDestination::ToCycle(ref cycle_name) => {
                let project_cycles = api_client.list_cycles(resolved_project.id()).await?;
                let mut matched_cycle_id = Option::None;
                for candidate_cycle in &project_cycles {
                    if candidate_cycle.display_name().eq_ignore_ascii_case(cycle_name) {
                        matched_cycle_id = Option::Some(candidate_cycle.id());
                    }
                }
                let resolved_cycle_id = match matched_cycle_id {
                    Option::Some(cycle_id) => cycle_id,
                    Option::None => {
                        let available: Vec<&str> = project_cycles
                            .iter()
                            .map(|c| c.display_name())
                            .collect();
                        anyhow::bail!(
                            "cycle '{}' not found — available: {}",
                            cycle_name,
                            available.join(", ")
                        );
                    }
                };

                let cycle_client = project_client.cycle_operations(resolved_cycle_id);
                cycle_client.add_work_item(fetched_work_item.id()).await?;

                println!(
                    "Moved {} to cycle \"{}\"",
                    parsed_identifier, cycle_name
                );
            }
            MoveDestination::ToModule(ref module_name) => {
                let project_modules = api_client.list_modules(resolved_project.id()).await?;
                let mut matched_module_id = Option::None;
                for candidate_module in &project_modules {
                    if candidate_module.display_name().eq_ignore_ascii_case(module_name) {
                        matched_module_id = Option::Some(candidate_module.id());
                    }
                }
                let resolved_module_id = match matched_module_id {
                    Option::Some(module_id) => module_id,
                    Option::None => {
                        let available: Vec<&str> = project_modules
                            .iter()
                            .map(|m| m.display_name())
                            .collect();
                        anyhow::bail!(
                            "module '{}' not found — available: {}",
                            module_name,
                            available.join(", ")
                        );
                    }
                };

                let module_client = project_client.module_operations(resolved_module_id);
                module_client.add_work_item(fetched_work_item.id()).await?;

                println!(
                    "Moved {} to module \"{}\"",
                    parsed_identifier, module_name
                );
            }
        }

        Ok(())
    }
}
