use std::process::Command;

use anyhow::{Context, Result};

use crate::api::{PlaneApiClient, WorkItemIdentifier};
use crate::config::resolve_plane_session;
use crate::git_context::{BranchInference, infer_work_item_from_branch};

/// Opens a GitHub pull request with the title and body prefilled from the current
/// branch's work item. Requires `gh` CLI to be installed and authenticated.
/// Usage: `plane pr` (infers work item from branch) or `plane pr NRO-123`
#[derive(clap::Args)]
pub struct OpenPullRequestArgs {
    /// Work item identifier (e.g. "NRO-123"). Inferred from git branch if omitted.
    identifier: Option<String>,
    /// Target branch for the PR (defaults to "main")
    #[arg(long, default_value = "main")]
    base: String,
}

/// Execution logic for the `plane pr` subcommand
impl OpenPullRequestArgs {
    /// Resolves the work item, builds a PR title and body with a Plane link,
    /// and invokes `gh pr create` to open the pull request on GitHub
    pub 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 pr 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 pr_title_builder = PullRequestTitleBuilder {
            work_item_identifier: &work_item_identifier,
        };
        let pr_title = pr_title_builder.build_from_title(fetched_work_item.title().as_str());

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

        let pr_body_builder = PullRequestBodyBuilder {
            plane_url: &plane_url,
            work_item_identifier: &work_item_identifier,
        };
        let pr_body = pr_body_builder.build_from_title(fetched_work_item.title().as_str());

        let gh_runner = GhCliRunner {
            pr_title: &pr_title,
            pr_body: &pr_body,
            base_branch: &self.base,
        };

        let created_pr_url = gh_runner.create_pull_request()?;

        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 pr_comment_text = format!("PR opened: {}", created_pr_url);
        item_client.post_comment(&pr_comment_text).await?;
        println!("  Commented on {}", work_item_identifier);

        Ok(())
    }
}

/// Constructs PR titles in the format `[NRO-123] Work item title`
struct PullRequestTitleBuilder<'a> {
    work_item_identifier: &'a WorkItemIdentifier,
}

/// PR title construction from work item metadata
impl<'a> PullRequestTitleBuilder<'a> {
    fn build_from_title(&self, title_text: &str) -> String {
        let mut pr_title = String::with_capacity(128);
        pr_title.push('[');
        pr_title.push_str(&self.work_item_identifier.to_string());
        pr_title.push_str("] ");
        pr_title.push_str(title_text);
        pr_title
    }
}

/// Constructs PR body text with a Plane link and work item reference
struct PullRequestBodyBuilder<'a> {
    plane_url: &'a str,
    work_item_identifier: &'a WorkItemIdentifier,
}

/// PR body construction with Plane link
impl<'a> PullRequestBodyBuilder<'a> {
    fn build_from_title(&self, title_text: &str) -> String {
        let mut pr_body = String::with_capacity(256);
        pr_body.push_str("## ");
        pr_body.push_str(&self.work_item_identifier.to_string());
        pr_body.push_str(": ");
        pr_body.push_str(title_text);
        pr_body.push_str("\n\n");
        pr_body.push_str("Plane: ");
        pr_body.push_str(self.plane_url);
        pr_body.push('\n');
        pr_body
    }
}

/// Invokes the `gh` CLI to create a pull request with pre-built title and body
struct GhCliRunner<'a> {
    pr_title: &'a str,
    pr_body: &'a str,
    base_branch: &'a str,
}

/// GitHub CLI subprocess execution for PR creation
impl<'a> GhCliRunner<'a> {
    /// Invokes `gh pr create` and returns the URL of the newly created pull request.
    /// The PR URL is printed to stdout for user visibility and also returned
    /// so callers can post it back to Plane as a comment.
    fn create_pull_request(&self) -> Result<String> {
        let gh_output = Command::new("gh")
            .args([
                "pr",
                "create",
                "--title",
                self.pr_title,
                "--body",
                self.pr_body,
                "--base",
                self.base_branch,
            ])
            .output()
            .context("failed to run gh — is the GitHub CLI installed?")?;

        let stdout_text = String::from_utf8_lossy(&gh_output.stdout);
        let stderr_text = String::from_utf8_lossy(&gh_output.stderr);

        if !gh_output.status.success() {
            anyhow::bail!("gh pr create failed: {}", stderr_text.trim());
        }

        print!("{}", stdout_text);
        if !stderr_text.is_empty() {
            eprint!("{}", stderr_text);
        }

        Ok(stdout_text.trim().to_owned())
    }
}
