use std::process::Command;

use anyhow::{Context, Result};

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

use super::entity_target::EntityTarget;

/// Opens a Plane entity in the default web browser by constructing the appropriate
/// Plane web UI URL. Supports work items, projects, workspaces, cycles, and modules.
/// Usage: `plane open NRO-123`, `plane open project nro`, `plane open workspace jakutainc`
#[derive(clap::Args)]
pub struct OpenInBrowserArgs {
    /// Entity type keyword (project, workspace, cycle, module) or work item ID (e.g. "NRO-123")
    target: String,
    /// Name or slug of the entity when target is a type keyword
    entity_name: Option<String>,
}

/// Execution logic for the `plane open` subcommand
impl OpenInBrowserArgs {
    /// Resolves the target entity, constructs the Plane web URL,
    /// and launches the default browser to display it
    pub async fn execute(self) -> Result<()> {
        let entity_name_text = match self.entity_name {
            Option::Some(name) => name,
            Option::None => String::new(),
        };

        let resolved_target = match self.target.as_str() {
            "project" => {
                if entity_name_text.is_empty() {
                    anyhow::bail!("'project' requires a slug — usage: plane open project nro");
                }
                EntityTarget::Project(entity_name_text)
            }
            "workspace" => {
                if entity_name_text.is_empty() {
                    anyhow::bail!(
                        "'workspace' requires a slug — usage: plane open workspace jakutainc"
                    );
                }
                EntityTarget::Workspace(entity_name_text)
            }
            "cycle" => {
                if entity_name_text.is_empty() {
                    anyhow::bail!(
                        "'cycle' requires a name — usage: plane open cycle \"Sprint 4\""
                    );
                }
                EntityTarget::Cycle(entity_name_text)
            }
            "module" => {
                if entity_name_text.is_empty() {
                    anyhow::bail!(
                        "'module' requires a name — usage: plane open module \"Auth System\""
                    );
                }
                EntityTarget::Module(entity_name_text)
            }
            bare_identifier => {
                let work_item_identifier: WorkItemIdentifier = bare_identifier.parse()?;
                EntityTarget::WorkItem(work_item_identifier)
            }
        };

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

        let url_builder = PlaneUrlBuilder { api_client: &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(ref slug) => {
                let mut workspace_url = String::with_capacity(64);
                workspace_url.push_str("https://app.plane.so/");
                workspace_url.push_str(slug);
                workspace_url.push('/');
                workspace_url
            }
            EntityTarget::Cycle(ref name) => {
                url_builder.cycle_url(name).await?
            }
            EntityTarget::Module(ref name) => {
                url_builder.module_url(name).await?
            }
        };

        launch_browser(&browser_url)?;

        let display_label = match resolved_target {
            EntityTarget::WorkItem(identifier) => format!("{}", identifier),
            EntityTarget::Project(slug) => format!("project {}", slug),
            EntityTarget::Workspace(slug) => format!("workspace {}", slug),
            EntityTarget::Cycle(name) => format!("cycle {}", name),
            EntityTarget::Module(name) => format!("module {}", name),
        };

        println!("Opened {} in browser", display_label);

        Ok(())
    }
}

/// Builds browser-facing Plane web UI URLs for various entity types.
/// Holds a reference to the authenticated API client for resolving slugs and names
/// to the UUIDs needed in Plane's URL structure.
pub(crate) struct PlaneUrlBuilder<'a> {
    api_client: &'a PlaneApiClient,
}

/// Construction for the Plane URL builder
impl<'a> PlaneUrlBuilder<'a> {
    /// Creates a URL builder from an authenticated API client
    pub(crate) fn new(api_client: &'a PlaneApiClient) -> Self {
        Self { api_client }
    }
}

/// URL construction methods for each supported entity type, each taking a single
/// identifier parameter and returning the full browser URL
impl<'a> PlaneUrlBuilder<'a> {
    /// Resolves a work item identifier to its full browser URL by looking up
    /// the project and work item UUIDs needed for Plane's URL structure
    pub(crate) async fn work_item_url(&self, work_item_identifier: &WorkItemIdentifier) -> Result<String> {
        let fetched_work_item = self
            .api_client
            .find_work_item_by_identifier(work_item_identifier)
            .await?;

        let mut browser_url = String::with_capacity(128);
        browser_url.push_str("https://app.plane.so/");
        browser_url.push_str(self.api_client.workspace_slug().as_str());
        browser_url.push_str("/projects/");
        browser_url.push_str(fetched_work_item.project_id().as_str());
        browser_url.push_str("/issues/");
        browser_url.push_str(fetched_work_item.id().as_str());
        Ok(browser_url)
    }

    /// Resolves a project slug to its browser URL showing the project issues view
    pub(crate) async fn project_url(&self, project_slug: &str) -> Result<String> {
        let parsed_slug = project_slug.parse()?;
        let resolved_project = self.api_client.find_project_by_slug(&parsed_slug).await?;

        let mut browser_url = String::with_capacity(128);
        browser_url.push_str("https://app.plane.so/");
        browser_url.push_str(self.api_client.workspace_slug().as_str());
        browser_url.push_str("/projects/");
        browser_url.push_str(resolved_project.id().as_str());
        browser_url.push_str("/issues/");
        Ok(browser_url)
    }

    /// Resolves a cycle name to its browser URL by finding the cycle UUID
    /// within the default project's cycle list
    pub(crate) async fn cycle_url(&self, cycle_name: &str) -> Result<String> {
        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_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().as_str().to_owned());
            }
        }

        let resolved_cycle_id = match matched_cycle_id {
            Option::Some(cycle_uuid) => cycle_uuid,
            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 mut browser_url = String::with_capacity(128);
        browser_url.push_str("https://app.plane.so/");
        browser_url.push_str(self.api_client.workspace_slug().as_str());
        browser_url.push_str("/projects/");
        browser_url.push_str(resolved_project.id().as_str());
        browser_url.push_str("/cycles/");
        browser_url.push_str(&resolved_cycle_id);
        Ok(browser_url)
    }

    /// Resolves a module name to its browser URL by finding the module UUID
    /// within the default project's module list
    pub(crate) async fn module_url(&self, module_name: &str) -> Result<String> {
        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_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().as_str().to_owned());
            }
        }

        let resolved_module_id = match matched_module_id {
            Option::Some(module_uuid) => module_uuid,
            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 mut browser_url = String::with_capacity(128);
        browser_url.push_str("https://app.plane.so/");
        browser_url.push_str(self.api_client.workspace_slug().as_str());
        browser_url.push_str("/projects/");
        browser_url.push_str(resolved_project.id().as_str());
        browser_url.push_str("/modules/");
        browser_url.push_str(&resolved_module_id);
        Ok(browser_url)
    }
}

/// Extracts the default project slug from the API client's session scope,
/// returning an error if no default project is configured
pub(crate) 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>"
            );
        }
    }
}

/// Launches the system default browser to display the given URL using
/// platform-specific commands (cmd/start on Windows, open on macOS, xdg-open on Linux)
pub(crate) fn launch_browser(target_url: &str) -> Result<()> {
    #[cfg(target_os = "windows")]
    {
        Command::new("cmd")
            .args(["/C", "start", target_url])
            .spawn()
            .context("failed to launch browser")?;
    }

    #[cfg(target_os = "macos")]
    {
        Command::new("open")
            .arg(target_url)
            .spawn()
            .context("failed to launch browser")?;
    }

    #[cfg(target_os = "linux")]
    {
        Command::new("xdg-open")
            .arg(target_url)
            .spawn()
            .context("failed to launch browser")?;
    }

    Ok(())
}
