use anyhow::Result;

use crate::api::{PlaneApiClient, RelationKind, RelationSpec, WorkItemIdentifier};
use crate::config::{ProjectScope, resolve_plane_session};
use super::work_item_fields::{
    WorkItemFieldArgs, WorkItemFieldArgsRaw, WorkItemFieldArgsTarget,
    WorkItemFieldArgsTargetParams,
};

/// Creates a new work item in the active project with inline flags for state,
/// priority, labels, assignee, and acceptance criteria.
/// When called without required flags, errors with usage guidance
/// (interactive mode via dialoguer will be added in a future version).
#[derive(clap::Args)]
pub struct CreateWorkItemArgs {
    /// Title for the new work item
    title: String,
    /// Project slug to create in (overrides the configured default project)
    #[arg(long)]
    project: Option<String>,
    /// Priority level: urgent, high, medium, low, none
    #[arg(long)]
    priority: Option<String>,
    /// State name to assign (matched case-insensitively against project states)
    #[arg(long)]
    state: Option<String>,
    /// Label name to attach (repeatable: --label "bug" --label "frontend")
    #[arg(long)]
    label: Vec<String>,
    /// Module name to assign (matched case-insensitively against project modules)
    #[arg(long)]
    module: Vec<String>,
    /// Parent work item identifier (e.g. --parent NRO-45)
    #[arg(long)]
    parent: Vec<String>,
    /// Start date in YYYY-MM-DD format
    #[arg(long = "start-date")]
    start_date: Vec<String>,
    /// Due date in YYYY-MM-DD format
    #[arg(long)]
    due: Vec<String>,
    /// Plain text description (wrapped in paragraph tags)
    #[arg(long)]
    description: Vec<String>,
    /// Estimate points (0-7)
    #[arg(long)]
    estimate: Vec<String>,
    /// Assignee UUID (repeatable: --assignee UUID --assignee UUID)
    #[arg(long)]
    assignee: Vec<String>,
    /// Acceptance criteria (repeatable: --ac "criterion 1" --ac "criterion 2")
    #[arg(long)]
    ac: Vec<String>,
    /// Mark as blocked by another item (e.g. --blocked-by NRO-45). Repeatable.
    #[arg(long = "blocked-by")]
    blocked_by: Vec<String>,
    /// Mark as blocking another item (e.g. --blocking NRO-45). Repeatable.
    #[arg(long)]
    blocking: Vec<String>,
    /// Link as relates-to another item (e.g. --relates-to NRO-45). Repeatable.
    #[arg(long = "relates-to")]
    relates_to: Vec<String>,
}

/// Execution logic for the `plane create` subcommand
impl CreateWorkItemArgs {
    /// Validates the provided fields against the project schema,
    /// creates the work item via the Plane API, and prints the new identifier
    pub async fn execute(self) -> Result<()> {
        let plane_session = resolve_plane_session()?;
        let api_client = PlaneApiClient::try_new(plane_session)?;

        let target_project_slug = match self.project {
            Option::Some(ref explicit_project) => explicit_project.parse()?,
            Option::None => 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>"
                    );
                }
            },
        };

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

        let mut request_fields = serde_json::Map::new();
        request_fields.insert(
            String::from("name"),
            serde_json::Value::String(self.title),
        );

        if let Option::Some(priority_text) = self.priority {
            request_fields.insert(
                String::from("priority"),
                serde_json::Value::String(priority_text.to_lowercase()),
            );
        }

        if let Option::Some(state_name) = self.state {
            let project_states = api_client.list_states(resolved_project.id()).await?;
            let mut matched_state_id = Option::None;
            for candidate_state in &project_states {
                if candidate_state
                    .display_name()
                    .eq_ignore_ascii_case(&state_name)
                {
                    matched_state_id = Option::Some(candidate_state.id().as_str().to_owned());
                }
            }
            match matched_state_id {
                Option::Some(state_uuid) => {
                    request_fields.insert(
                        String::from("state"),
                        serde_json::Value::String(state_uuid),
                    );
                }
                Option::None => {
                    let available: Vec<&str> =
                        project_states.iter().map(|s| s.display_name()).collect();
                    anyhow::bail!(
                        "state '{}' not found — available: {}",
                        state_name,
                        available.join(", ")
                    );
                }
            }
        }

        let shared_fields = WorkItemFieldArgs::from_raw_args(WorkItemFieldArgsRaw {
            labels: self.label,
            module: self.module,
            parent: self.parent,
            start_date: self.start_date,
            due: self.due,
            description: self.description,
            estimate: self.estimate,
            assignees: self.assignee,
        })?;
        let apply_target = WorkItemFieldArgsTarget::new(WorkItemFieldArgsTargetParams {
            request_fields: &mut request_fields,
            api_client: &api_client,
            project_id: resolved_project.id(),
        });
        shared_fields.apply_to(apply_target).await?;

        let request_body_json = serde_json::Value::Object(request_fields).to_string();

        let project_client = api_client.project_operations(resolved_project.id());
        let created_item = project_client.create_work_item(&request_body_json).await?;

        let created_identifier = format!(
            "{}-{}",
            resolved_project.identifier(),
            created_item.sequence_id().as_u32()
        );

        println!(
            "Created {}: {}",
            created_identifier,
            created_item.title().as_str()
        );

        let item_client = project_client.work_item_operations(created_item.id());

        if !self.ac.is_empty() {
            let mut task_list_html = String::from("<ul data-type=\"taskList\">");
            for criterion_text in &self.ac {
                task_list_html.push_str(
                    "<li data-type=\"taskItem\" data-checked=\"false\"><p>",
                );
                task_list_html.push_str(criterion_text);
                task_list_html.push_str("</p></li>");
            }
            task_list_html.push_str("</ul>");

            let mut ac_request_fields = serde_json::Map::new();
            ac_request_fields.insert(
                String::from("description_html"),
                serde_json::Value::String(task_list_html),
            );
            let ac_request_body =
                serde_json::Value::Object(ac_request_fields).to_string();

            item_client.update_fields(&ac_request_body).await?;

            println!(
                "  Added {} acceptance criteria to {}",
                self.ac.len(),
                created_identifier
            );
        }

        let relation_pairs: Vec<(&Vec<String>, RelationKind)> = vec![
            (&self.blocked_by, RelationKind::BlockedBy),
            (&self.blocking, RelationKind::Blocking),
            (&self.relates_to, RelationKind::RelatesTo),
        ];

        for (target_identifiers, relation_kind) in relation_pairs {
            for target_identifier_text in target_identifiers {
                let target_identifier: WorkItemIdentifier =
                    target_identifier_text.parse()?;
                let target_work_item = api_client
                    .find_work_item_by_identifier(&target_identifier)
                    .await?;
                let target_id = target_work_item.into_id();
                let relation_spec =
                    RelationSpec::of_kind(relation_kind).targeting(target_id);
                item_client.add_relation(relation_spec).await?;
                println!(
                    "  {} {} {}",
                    created_identifier, relation_kind, target_identifier
                );
            }
        }

        Ok(())
    }
}
