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

use crate::api::{
    PlaneApiClient, PlaneProject, PlaneState, PlaneWorkItem, RelatedIssueSummary, RelationKind,
    ScheduledDate, StateGroup, WorkItemDescription, WorkItemIdentifier, WorkItemPriority,
};
use crate::config::{ProjectScope, resolve_plane_session};
use crate::git_context::{BranchInference, infer_work_item_from_branch};

use super::entity_target::EntityTarget;

/// Displays full detail of a Plane entity. Supports work items, projects, cycles, and modules.
/// Usage: `plane show NRO-123`, `plane show project nro`, `plane show cycle "Sprint 4"`.
/// When no arguments are given, infers the work item from the current git branch name.
/// Pass --web to open the entity in the browser instead of printing to terminal.
#[derive(clap::Args)]
pub struct ShowWorkItemArgs {
    /// Entity type keyword (project, cycle, module) or work item ID (e.g. "NRO-123")
    target: Option<String>,
    /// Name or slug of the entity when target is a type keyword
    entity_name: Option<String>,
    /// Open the entity in the default browser instead of printing
    #[arg(long)]
    web: Option<String>,
}

/// Execution logic for the `plane show` subcommand
impl ShowWorkItemArgs {
    /// Resolves the target entity and renders its full detail view
    pub async fn execute(self) -> Result<()> {
        let plane_session = resolve_plane_session()?;
        let api_client = PlaneApiClient::try_new(plane_session)?;

        let resolved_target = match self.target {
            Option::None => {
                let inferred = match infer_work_item_from_branch() {
                    BranchInference::Resolved(identifier) => identifier,
                    BranchInference::NotInGitRepo => {
                        anyhow::bail!(
                            "no target provided and not in a git repo — usage: plane show NRO-123"
                        );
                    }
                    BranchInference::NoIdentifierInBranch(branch_name) => {
                        anyhow::bail!(
                            "no target provided and branch '{}' has no work item ID",
                            branch_name
                        );
                    }
                };
                EntityTarget::WorkItem(inferred)
            }
            Option::Some(ref target_text) => {
                let entity_name_text = match self.entity_name {
                    Option::Some(ref name) => name.as_str(),
                    Option::None => "",
                };

                match target_text.as_str() {
                    "project" => {
                        if entity_name_text.is_empty() {
                            anyhow::bail!(
                                "'project' requires a slug — usage: plane show project nro"
                            );
                        }
                        EntityTarget::Project(entity_name_text.to_owned())
                    }
                    "cycle" => {
                        if entity_name_text.is_empty() {
                            anyhow::bail!(
                                "'cycle' requires a name — usage: plane show cycle \"Sprint 4\""
                            );
                        }
                        EntityTarget::Cycle(entity_name_text.to_owned())
                    }
                    "module" => {
                        if entity_name_text.is_empty() {
                            anyhow::bail!(
                                "'module' requires a name — usage: plane show module \"Auth\""
                            );
                        }
                        EntityTarget::Module(entity_name_text.to_owned())
                    }
                    bare_identifier => {
                        let identifier: WorkItemIdentifier = bare_identifier.parse()?;
                        EntityTarget::WorkItem(identifier)
                    }
                }
            }
        };

        match self.web {
            Option::Some(_) => {
                let url_builder = super::open::PlaneUrlBuilder::new(&api_client);
                let browser_url = match resolved_target {
                    EntityTarget::WorkItem(ref identifier) => {
                        url_builder.work_item_url(identifier).await?
                    }
                    EntityTarget::Project(ref slug) => {
                        url_builder.project_url(slug).await?
                    }
                    EntityTarget::Workspace(_) => {
                        anyhow::bail!(
                            "plane show workspace --web is not supported — \
                             use plane open workspace"
                        )
                    }
                    EntityTarget::Cycle(ref name) => {
                        url_builder.cycle_url(name).await?
                    }
                    EntityTarget::Module(ref name) => {
                        url_builder.module_url(name).await?
                    }
                };
                super::open::launch_browser(&browser_url)?;
                println!("Opened in browser");
                Ok(())
            }
            Option::None => {
                let detail_viewer = EntityDetailViewer { api_client: &api_client };

                match resolved_target {
                    EntityTarget::WorkItem(identifier) => {
                        detail_viewer.render_work_item(&identifier).await
                    }
                    EntityTarget::Project(slug) => detail_viewer.render_project(&slug).await,
                    EntityTarget::Workspace(_slug) => {
                        anyhow::bail!(
                            "plane show workspace is not supported — use plane open workspace"
                        )
                    }
                    EntityTarget::Cycle(name) => detail_viewer.render_cycle(&name).await,
                    EntityTarget::Module(name) => detail_viewer.render_module(&name).await,
                }
            }
        }
    }
}

/// Renders full detail views for Plane entities by fetching data through
/// the authenticated API client and formatting terminal output.
struct EntityDetailViewer<'a> {
    api_client: &'a PlaneApiClient,
}

/// Work item, project, cycle, and module detail rendering via the Plane API
impl<'a> EntityDetailViewer<'a> {
    async fn render_work_item(&self, work_item_identifier: &WorkItemIdentifier) -> Result<()> {
        let project_identifier_code = work_item_identifier.project_identifier();
        let target_project_slug = project_identifier_code.to_lowercase();
        let target_project_slug_parsed = target_project_slug.parse()?;

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

        let project_states = self.api_client.list_states(resolved_project.id()).await?;
        let work_items = self.api_client.list_work_items(resolved_project.id()).await?;

        let target_sequence = work_item_identifier.sequence_number().as_u32();
        let mut matched_work_item: Option<&PlaneWorkItem> = Option::None;
        for candidate_item in &work_items {
            if candidate_item.sequence_id().as_u32() == target_sequence {
                matched_work_item = Option::Some(candidate_item);
            }
        }

        let fetched_work_item = match matched_work_item {
            Option::Some(found_item) => found_item,
            Option::None => {
                anyhow::bail!(
                    "work item {} not found in project {}",
                    work_item_identifier,
                    resolved_project.identifier()
                );
            }
        };

        let field_renderer = WorkItemFieldRenderer {
            project_states: &project_states,
            project_identifier_code,
        };

        field_renderer.print_detail(fetched_work_item);

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

        match item_client.list_relations().await {
            Ok(relation_map) => {
                let has_any_relations = relation_map
                    .values()
                    .any(|related_issues| !related_issues.is_empty());
                if has_any_relations {
                    let workspace_projects = self.api_client.list_projects().await?;
                    println!();
                    println!("  {}", "Relations:".bold());
                    let relation_printer = InlineRelationPrinter {
                        workspace_projects: &workspace_projects,
                    };
                    relation_printer.print(&relation_map);
                }
            }
            Err(_relation_fetch_error) => {
                println!(
                    "\n  {} (internal API may not support API key auth)",
                    "Relations: unavailable".dimmed()
                );
            }
        }

        Ok(())
    }

    async fn render_project(&self, project_slug: &str) -> Result<()> {
        let parsed_slug = project_slug.parse()?;
        let resolved_project = self.api_client.find_project_by_slug(&parsed_slug).await?;
        let project_states = self.api_client.list_states(resolved_project.id()).await?;
        let work_items = self.api_client.list_work_items(resolved_project.id()).await?;

        println!(
            "{} ({})",
            resolved_project.display_name().bold(),
            resolved_project.identifier()
        );
        println!("  Work items: {}", work_items.len());
        println!();

        for state_entry in &project_states {
            let count = work_items
                .iter()
                .filter(|item| item.workflow_state_id() == state_entry.id())
                .count();
            if count > 0 {
                let state_display = colorize_state_label(state_entry);
                println!("  {:20} {}", state_display, count);
            }
        }

        Ok(())
    }

    async fn render_cycle(&self, cycle_name: &str) -> Result<()> {
        let default_slug = resolve_default_project(self.api_client)?;
        let resolved_project = self.api_client.find_project_by_slug(&default_slug).await?;
        let project_cycles = self.api_client.list_cycles(resolved_project.id()).await?;

        let mut matched_cycle = Option::None;
        for candidate_cycle in &project_cycles {
            if candidate_cycle.display_name().eq_ignore_ascii_case(cycle_name) {
                matched_cycle = Option::Some(candidate_cycle);
            }
        }

        let found_cycle = match matched_cycle {
            Option::Some(cycle_entry) => cycle_entry,
            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(", ")
                );
            }
        };

        println!("{}", found_cycle.display_name().bold());
        if !found_cycle.start_date().is_empty() {
            println!("  Start: {}", found_cycle.start_date());
        }
        if !found_cycle.end_date().is_empty() {
            println!("  End:   {}", found_cycle.end_date());
        }

        Ok(())
    }

    async fn render_module(&self, module_name: &str) -> Result<()> {
        let default_slug = resolve_default_project(self.api_client)?;
        let resolved_project = self.api_client.find_project_by_slug(&default_slug).await?;
        let project_modules = self.api_client.list_modules(resolved_project.id()).await?;

        let mut matched_module = Option::None;
        for candidate_module in &project_modules {
            if candidate_module.display_name().eq_ignore_ascii_case(module_name) {
                matched_module = Option::Some(candidate_module);
            }
        }

        let found_module = match matched_module {
            Option::Some(module_entry) => module_entry,
            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(", ")
                );
            }
        };

        println!("{}", found_module.display_name().bold());
        if !found_module.module_description().is_empty() {
            println!("  {}", found_module.module_description());
        }

        Ok(())
    }
}

fn resolve_default_project(
    api_client: &PlaneApiClient,
) -> Result<crate::config::ProjectSlug> {
    match api_client.project_scope() {
        ProjectScope::Scoped(default_project) => default_project.as_str().parse(),
        ProjectScope::ExplicitOnly => {
            anyhow::bail!(
                "cycles and modules require a default project — run: \
                 plane config --project <slug>"
            );
        }
    }
}

/// Holds project context for rendering a single work item's full detail view
struct WorkItemFieldRenderer<'a> {
    project_states: &'a [PlaneState],
    project_identifier_code: &'a str,
}

/// Field-level rendering for work item detail views with state and priority coloring
impl<'a> WorkItemFieldRenderer<'a> {
    fn print_detail(&self, work_item: &PlaneWorkItem) {
        let identifier_display = format!(
            "{}-{}",
            self.project_identifier_code,
            work_item.sequence_id().as_u32()
        );

        let state_display = self.resolve_state_name(work_item);
        let priority_display = format_priority_label(work_item.priority());

        println!(
            "{} {}",
            identifier_display.bold(),
            work_item.title().as_str().bold()
        );
        println!("  State:     {}", state_display);
        println!("  Priority:  {}", priority_display);

        match work_item.target_date() {
            ScheduledDate::Scheduled(date_text) => {
                println!("  Due:       {}", date_text);
            }
            ScheduledDate::Unscheduled => {}
        }

        println!(
            "  Assignees: {}",
            work_item.assignee_ids().len()
        );
        println!("  Created:   {}", work_item.created_at());

        match work_item.description_stripped() {
            WorkItemDescription::Present(description_text) => {
                println!();
                println!("{}", description_text);
            }
            WorkItemDescription::Absent => {}
        }
    }

    fn resolve_state_name(&self, target_work_item: &PlaneWorkItem) -> String {
        for candidate_state in self.project_states {
            if candidate_state.id() == target_work_item.workflow_state_id() {
                return colorize_state_label(candidate_state);
            }
        }
        target_work_item.workflow_state_id().to_string()
    }
}

fn colorize_state_label(resolved_state: &PlaneState) -> String {
    match resolved_state.state_group() {
        StateGroup::Backlog => resolved_state.display_name().dimmed().to_string(),
        StateGroup::Unstarted => resolved_state.display_name().white().to_string(),
        StateGroup::Started => resolved_state.display_name().yellow().to_string(),
        StateGroup::Completed => resolved_state.display_name().green().to_string(),
        StateGroup::Cancelled => resolved_state.display_name().red().to_string(),
    }
}

fn format_priority_label(work_item_priority: WorkItemPriority) -> String {
    match work_item_priority {
        WorkItemPriority::Urgent => "Urgent".red().bold().to_string(),
        WorkItemPriority::High => "High".red().to_string(),
        WorkItemPriority::Medium => "Medium".yellow().to_string(),
        WorkItemPriority::Low => "Low".dimmed().to_string(),
        WorkItemPriority::None => String::from("—"),
    }
}

/// Renders relation groups inline in the work item detail view, resolving
/// project UUIDs to identifier codes via the workspace project list
struct InlineRelationPrinter<'a> {
    workspace_projects: &'a [PlaneProject],
}

/// Inline relation rendering for the show detail view
impl<'a> InlineRelationPrinter<'a> {
    /// Prints each non-empty relation group with the relation kind, target identifier,
    /// and title. Silently skips empty groups.
    fn print(
        &self,
        relation_map: &std::collections::HashMap<RelationKind, Vec<RelatedIssueSummary>>,
    ) {
        for (relation_kind, related_issues) in relation_map {
            if related_issues.is_empty() {
                continue;
            }
            for related_issue in related_issues {
                let project_code = self.resolve_project_code(related_issue.project_id());
                println!(
                    "    {} {}-{} {}",
                    relation_kind.to_string().yellow(),
                    project_code,
                    related_issue.sequence_number().as_u32(),
                    related_issue.title().dimmed()
                );
            }
        }
    }

    fn resolve_project_code(&self, target_project_id: &crate::api::ProjectId) -> &str {
        for candidate_project in self.workspace_projects {
            if candidate_project.id() == target_project_id {
                return candidate_project.identifier();
            }
        }
        "???"
    }
}
