use anyhow::Result;
use colored::Colorize;

use crate::api::{
    PlaneApiClient, PlaneProject, RelatedIssueSummary, RelationKind, RelationSpec,
    WorkItemIdentifier,
};
use crate::config::resolve_plane_session;
use crate::git_context::{BranchInference, infer_work_item_from_branch};

/// Entry point for the `plane relation` subcommand tree, dispatching to list, add,
/// or remove operations on work item relations (blocked-by, blocking, duplicate,
/// relates-to, and timeline dependencies) via the internal Plane API.
/// Usage: `plane relation ls NRO-123`, `plane relation add NRO-123 NRO-456 --type blocked-by`
#[derive(clap::Args)]
pub struct RelationCommandArgs {
    /// The relation action to perform
    #[command(subcommand)]
    action: RelationAction,
}

/// Available relation subcommands: listing, creating, and removing relations
#[derive(clap::Subcommand)]
enum RelationAction {
    /// List all relations for a work item
    #[command(name = "ls")]
    List(ListRelationsArgs),
    /// Add a relation between two work items
    Add(AddRelationArgs),
    /// Remove a relation between two work items
    #[command(name = "rm")]
    Remove(RemoveRelationArgs),
}

/// Arguments for listing all relations on a single work item
#[derive(clap::Args)]
struct ListRelationsArgs {
    /// Source work item identifier (e.g. "NRO-123"). Inferred from branch if omitted.
    identifier: Option<String>,
}

/// Arguments for creating a new relation between two work items
#[derive(clap::Args)]
struct AddRelationArgs {
    /// Source work item identifier (e.g. "NRO-123")
    source: String,
    /// Target work item to relate to (e.g. "NRO-456")
    target: String,
    /// Relation type: blocked-by, blocking, duplicate, relates-to, etc.
    #[arg(long = "type")]
    relation_type: RelationKind,
}

/// Arguments for removing an existing relation between two work items
#[derive(clap::Args)]
struct RemoveRelationArgs {
    /// Source work item identifier (e.g. "NRO-123")
    source: String,
    /// Target work item to unrelate (e.g. "NRO-456")
    target: String,
    /// Relation type to remove
    #[arg(long = "type")]
    relation_type: RelationKind,
}

/// Top-level dispatch for the relation command tree
impl RelationCommandArgs {
    /// Routes to the appropriate relation subcommand based on the parsed CLI action
    pub async fn execute(self) -> Result<()> {
        match self.action {
            RelationAction::List(list_args) => list_args.execute().await,
            RelationAction::Add(add_args) => add_args.execute().await,
            RelationAction::Remove(remove_args) => remove_args.execute().await,
        }
    }
}

/// Fetches and displays all relations grouped by kind for the specified work item
impl ListRelationsArgs {
    /// Resolves the work item, fetches relations from the internal API, resolves
    /// project codes for display, and prints the grouped relation listing
    async fn execute(self) -> Result<()> {
        let work_item_identifier: WorkItemIdentifier = match self.identifier {
            Option::Some(ref identifier_text) => identifier_text.parse()?,
            Option::None => match infer_work_item_from_branch() {
                BranchInference::Resolved(inferred_identifier) => inferred_identifier,
                BranchInference::NotInGitRepo => {
                    anyhow::bail!(
                        "no identifier provided and not in a git repo — \
                         usage: plane relation ls NRO-123"
                    );
                }
                BranchInference::NoIdentifierInBranch(branch_name) => {
                    anyhow::bail!(
                        "no identifier provided and branch '{}' does not contain a work item ID",
                        branch_name
                    );
                }
            },
        };

        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(&work_item_identifier)
            .await?;

        let project_client = api_client.project_operations(fetched_work_item.project_id());
        let item_client = project_client.work_item_operations(fetched_work_item.id());

        let relation_map = item_client.list_relations().await?;

        let workspace_projects = api_client.list_projects().await?;

        let relation_printer = RelationListPrinter {
            work_item_identifier: &work_item_identifier,
            workspace_projects: &workspace_projects,
        };
        relation_printer.print_grouped_relations(&relation_map);

        Ok(())
    }
}

/// Resolves both source and target identifiers, then creates the relation
impl AddRelationArgs {
    /// Looks up both work items by their human-readable identifiers, constructs
    /// a relation spec via the builder, and posts it to the internal API
    async fn execute(self) -> Result<()> {
        let source_identifier: WorkItemIdentifier = self.source.parse()?;
        let target_identifier: WorkItemIdentifier = self.target.parse()?;

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

        let source_work_item = api_client
            .find_work_item_by_identifier(&source_identifier)
            .await?;
        let target_work_item = api_client
            .find_work_item_by_identifier(&target_identifier)
            .await?;

        let project_client = api_client.project_operations(source_work_item.project_id());
        let item_client = project_client.work_item_operations(source_work_item.id());

        let target_id = target_work_item.into_id();
        let relation_spec = RelationSpec::of_kind(self.relation_type)
            .targeting(target_id);

        item_client.add_relation(relation_spec).await?;

        println!(
            "Added: {} {} {}",
            source_identifier, self.relation_type, target_identifier
        );

        Ok(())
    }
}

/// Resolves both source and target identifiers, then removes the relation
impl RemoveRelationArgs {
    /// Looks up both work items by their human-readable identifiers, constructs
    /// a relation spec via the builder, and sends the removal to the internal API
    async fn execute(self) -> Result<()> {
        let source_identifier: WorkItemIdentifier = self.source.parse()?;
        let target_identifier: WorkItemIdentifier = self.target.parse()?;

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

        let source_work_item = api_client
            .find_work_item_by_identifier(&source_identifier)
            .await?;
        let target_work_item = api_client
            .find_work_item_by_identifier(&target_identifier)
            .await?;

        let project_client = api_client.project_operations(source_work_item.project_id());
        let item_client = project_client.work_item_operations(source_work_item.id());

        let target_id = target_work_item.into_id();
        let relation_spec = RelationSpec::of_kind(self.relation_type)
            .targeting(target_id);

        item_client.remove_relation(relation_spec).await?;

        println!(
            "Removed: {} {} {}",
            source_identifier, self.relation_type, target_identifier
        );

        Ok(())
    }
}

/// Renders the relation listing to the terminal, resolving project UUIDs to
/// human-readable identifier codes for each related issue via linear search
/// over the workspace project list
struct RelationListPrinter<'a> {
    work_item_identifier: &'a WorkItemIdentifier,
    workspace_projects: &'a [PlaneProject],
}

/// Grouped relation output rendering and project code resolution
impl<'a> RelationListPrinter<'a> {
    /// Prints each non-empty relation group with formatted issue identifiers and titles.
    /// Groups with zero related issues are silently omitted. Prints "(no relations)"
    /// if the work item has no relations in any group.
    fn print_grouped_relations(
        &self,
        relation_map: &std::collections::HashMap<RelationKind, Vec<RelatedIssueSummary>>,
    ) {
        let mut has_any_relations = false;

        println!(
            "{}: Relations\n",
            self.work_item_identifier.to_string().bold()
        );

        for (relation_kind, related_issues) in relation_map {
            if related_issues.is_empty() {
                continue;
            }
            has_any_relations = true;

            println!("  {}:", relation_kind.to_string().yellow());
            for related_issue in related_issues {
                let project_code = self.resolve_project_code(related_issue.project_id());
                println!(
                    "    {}-{}  {}",
                    project_code,
                    related_issue.sequence_number().as_u32(),
                    related_issue.title()
                );
            }
            println!();
        }

        if !has_any_relations {
            println!("  (no relations)");
        }
    }

    /// Searches the workspace project list for the given project UUID and returns
    /// the identifier code (e.g. "NRO"). Returns "???" if the project is not found.
    fn resolve_project_code(&self, target_project_id: &crate::api::ProjectId) -> &str {
        for project_entry in self.workspace_projects {
            if project_entry.id() == target_project_id {
                return project_entry.identifier();
            }
        }
        "???"
    }
}
