use super::CIExit;
use crate::ops::{
    git_signature_ops::extract_commits, signature_ops::verify_commits,
    trust_fetcher::fetch_trust_list,
};
use crate::Error;
use clap::Parser;
use octocrate::{APIConfig, GitHubAPI, PersonalAccessToken};
use owo_colors::OwoColorize;
use std::env;

#[derive(Debug, Parser, Clone)]
pub struct VerifySignatures {
    /// Base ref for commit range
    #[clap(long, default_value = "origin/main")]
    pub base: String,

    /// Head ref for commit range
    #[clap(long, default_value = "HEAD")]
    pub head: String,

    /// Repository owner (auto-detected if not provided)
    #[clap(long)]
    pub repo_owner: Option<String>,

    /// Repository name (auto-detected if not provided)
    #[clap(long)]
    pub repo_name: Option<String>,

    /// Git fetch depth
    #[clap(long, default_value = "200")]
    pub fetch_depth: usize,

    /// Fail if trusted identities have unsigned commits
    #[clap(long, default_value_t = true)]
    pub fail_on_unsigned: bool,

    /// Post verification results as a PR comment
    #[clap(long)]
    pub post_comment: bool,

    /// PR number to comment on (required if --post-comment is used)
    #[clap(long, required_if_eq("post_comment", "true"))]
    pub pr_number: Option<u64>,

    /// GitHub token for read-only access (fetching trust list)
    /// If not provided, will use GITHUB_TOKEN env var
    #[clap(long)]
    pub github_token: Option<String>,
}

impl VerifySignatures {
    pub async fn run_verify(self) -> Result<CIExit, Error> {
        log::info!("=== Commit Signature Verification ===");

        // Open git repository
        let git_repo = git2::Repository::open(".")?;

        // Get owner and repo (auto-detect from git config if not provided)
        let (owner, repo) = detect_repository(&git_repo, &self.repo_owner, &self.repo_name)?;

        // Initialize GitHub client for read-only operations (fetching trust list)
        // Uses --github-token flag or falls back to GITHUB_TOKEN env var
        let env_token = env::var("GITHUB_TOKEN").ok();
        let token = self.github_token.as_deref().or(env_token.as_deref());
        let readonly_client = initialize_github_client_with_token(
            token,
            "Read-only GitHub token not provided (use --github-token or set GITHUB_TOKEN env var)",
        )?;

        println!("\n{}\n", "=== Commit Signature Verification ===".bold());
        println!("Repository: {owner}/{repo}\n");

        // Step 1: Fetch trust list from GitHub
        log::info!("Fetching trust list from GitHub...");
        let trust_map = fetch_trust_list(&readonly_client, &owner, &repo).await?;

        // Step 2: Extract commits from git
        log::info!("Extracting commits from {}..{}", self.base, self.head);
        let commits = extract_commits(&git_repo, &self.base, &self.head)?;

        if commits.is_empty() {
            println!("{}\n", "ℹ  No commits to verify".yellow());
            return Ok(CIExit::VerificationPassed);
        }

        println!("ℹ  Found {} commit(s) to verify\n", commits.len());

        // Step 3: Verify commits
        let (results, summary) = verify_commits(commits, &trust_map);

        // Step 4: Display results (privacy-safe)
        display_results(&results);

        // Step 5: Display summary
        display_summary(&summary, &results);

        // Early return on failure if requested
        if summary.failures > 0 && self.fail_on_unsigned {
            return Err(Error::SignatureVerificationFailed(summary.failures));
        }

        // Step 6: Post PR comment if requested
        if self.post_comment {
            if let Some(pr_number) = self.pr_number {
                log::info!("Posting verification results to PR #{pr_number}");

                // Create a separate client with write access for posting comments
                // This requires GITHUB_TOKEN env var with write permissions
                match initialize_github_client_from_env() {
                    Ok(write_client) => {
                        if let Err(e) = post_verification_comment(
                            &write_client,
                            &owner,
                            &repo,
                            pr_number,
                            &results,
                            &summary,
                        )
                        .await
                        {
                            log::warn!("Failed to post PR comment: {e}");
                            eprintln!("⚠  Warning: Failed to post PR comment: {e}");
                        }
                    }
                    Err(e) => {
                        log::warn!("Cannot post PR comment - GITHUB_TOKEN env var not set for write access: {e}");
                        eprintln!("⚠  Warning: Cannot post PR comment - GITHUB_TOKEN env var required for write access");
                    }
                }
            }
        }

        if summary.failures > 0 {
            Err(Error::SignatureVerificationFailed(summary.failures))
        } else {
            Ok(CIExit::VerificationPassed)
        }
    }
}

/// Post verification results as a PR comment
async fn post_verification_comment(
    github: &GitHubAPI,
    owner: &str,
    repo: &str,
    pr_number: u64,
    results: &[crate::ops::signature_ops::VerificationResult],
    summary: &crate::ops::signature_ops::VerificationSummary,
) -> Result<(), Error> {
    let comment = build_comment(results, summary)?;
    post_comment_to_github(github, owner, repo, pr_number, &comment).await?;
    Ok(())
}

/// Build the verification comment body
fn build_comment(
    results: &[crate::ops::signature_ops::VerificationResult],
    summary: &crate::ops::signature_ops::VerificationSummary,
) -> Result<String, Error> {
    use std::fmt::Write;

    let mut comment = String::new();
    let write_err = |e| Error::GpgError(format!("Failed to write comment: {e}"));

    if summary.failures > 0 {
        writeln!(&mut comment, "## ❌ Commit Signature Verification - FAILED")
            .map_err(write_err)?;
        writeln!(&mut comment).map_err(write_err)?;
        writeln!(
            &mut comment,
            "**⚠️ Security Alert:** One or more commits failed signature verification."
        )
        .map_err(write_err)?;
        writeln!(&mut comment).map_err(write_err)?;

        // List failed commits
        writeln!(&mut comment, "### Failed Commits").map_err(write_err)?;
        writeln!(&mut comment).map_err(write_err)?;

        write_failed_commits(&mut comment, results).map_err(write_err)?;

        writeln!(&mut comment).map_err(write_err)?;
        write_summary(&mut comment, summary, true).map_err(write_err)?;

        if summary.external_contributors > 0 {
            writeln!(&mut comment).map_err(write_err)?;
            write_external_contributors(&mut comment, results).map_err(write_err)?;
        }

        writeln!(&mut comment).map_err(write_err)?;
        write_action_required(&mut comment).map_err(write_err)?;
    } else {
        writeln!(
            &mut comment,
            "## ✅ Commit Signature Verification - Success"
        )
        .map_err(write_err)?;
        writeln!(&mut comment).map_err(write_err)?;
        writeln!(&mut comment, "All commits have been verified successfully.")
            .map_err(write_err)?;
        writeln!(&mut comment).map_err(write_err)?;
        write_summary(&mut comment, summary, false).map_err(write_err)?;

        if summary.external_contributors > 0 {
            writeln!(&mut comment).map_err(write_err)?;
            write_external_contributors(&mut comment, results).map_err(write_err)?;
        }

        writeln!(&mut comment).map_err(write_err)?;
        writeln!(&mut comment, "No impersonation attempts detected.").map_err(write_err)?;
    }

    Ok(comment)
}

/// Write failed commits section
fn write_failed_commits(
    comment: &mut String,
    results: &[crate::ops::signature_ops::VerificationResult],
) -> std::fmt::Result {
    use std::fmt::Write;

    for result in results.iter().filter(|r| !r.passed) {
        let sha_short = &result.commit.sha[..8.min(result.commit.sha.len())];
        writeln!(
            comment,
            "- `{}` {} - {}",
            sha_short,
            result.commit.subject,
            result.reason.display_message()
        )?;
    }
    Ok(())
}

/// Write summary section
fn write_summary(
    comment: &mut String,
    summary: &crate::ops::signature_ops::VerificationSummary,
    include_failures: bool,
) -> std::fmt::Result {
    use std::fmt::Write;

    writeln!(comment, "### Summary")?;
    writeln!(
        comment,
        "- **Commits checked**: {}",
        summary.commits_checked
    )?;
    writeln!(
        comment,
        "- **Trusted verified**: {}",
        summary.trusted_verified
    )?;
    writeln!(
        comment,
        "- **External contributors**: {}",
        summary.external_contributors
    )?;
    if include_failures {
        writeln!(comment, "- **Failures**: ❗ {}", summary.failures)?;
    }
    Ok(())
}

/// Write action required section
fn write_action_required(comment: &mut String) -> std::fmt::Result {
    use std::fmt::Write;

    writeln!(comment, "### Action Required")?;
    writeln!(comment, "- Review the failed commits immediately")?;
    writeln!(comment, "- Verify the committer's identity")?;
    writeln!(comment, "- **DO NOT MERGE** if impersonation is suspected")?;
    Ok(())
}

/// Write external contributors table section
fn write_external_contributors(
    comment: &mut String,
    results: &[crate::ops::signature_ops::VerificationResult],
) -> std::fmt::Result {
    use crate::ops::signature_ops::VerificationReason;
    use std::fmt::Write;

    writeln!(comment, "### External Contributors")?;
    writeln!(comment)?;
    writeln!(comment, "| Commit | Author | Status |")?;
    writeln!(comment, "|--------|--------|--------|")?;

    for result in results.iter().filter(|r| {
        matches!(
            r.reason,
            VerificationReason::ExternalUnsigned | VerificationReason::ExternalSigned
        )
    }) {
        let sha_short = &result.commit.sha[..8.min(result.commit.sha.len())];
        let status = match &result.reason {
            VerificationReason::ExternalUnsigned => "Unsigned (expected for bots/external)",
            VerificationReason::ExternalSigned => "Signed",
            _ => "",
        };
        writeln!(
            comment,
            "| `{}` | `{}` | {} |",
            sha_short, result.commit.author_email, status
        )?;
    }
    Ok(())
}

/// Post comment to GitHub using octocrate issues API
/// Updates existing comment if found, otherwise creates a new one
async fn post_comment_to_github(
    github: &GitHubAPI,
    owner: &str,
    repo: &str,
    pr_number: u64,
    comment: &str,
) -> Result<(), Error> {
    use octocrate::issues::create_comment::Request as CreateRequest;
    use octocrate::issues::update_comment::Request as UpdateRequest;

    // First, try to find an existing verification comment
    let existing_comment_id =
        find_existing_verification_comment(github, owner, repo, pr_number).await?;

    if let Some(comment_id) = existing_comment_id {
        // Update existing comment
        log::info!("Updating existing PR comment (ID: {comment_id})");
        let update_body = UpdateRequest {
            body: comment.to_string(),
        };

        github
            .issues
            .update_comment(owner, repo, comment_id)
            .body(&update_body)
            .send()
            .await
            .map_err(|e| Error::GpgError(format!("Failed to update PR comment: {e}")))?;
    } else {
        // Create new comment
        log::info!("Creating new PR comment");
        let create_body = CreateRequest {
            body: comment.to_string(),
        };

        github
            .issues
            .create_comment(owner, repo, pr_number as i64)
            .body(&create_body)
            .send()
            .await
            .map_err(|e| Error::GpgError(format!("Failed to create PR comment: {e}")))?;
    }

    Ok(())
}

/// Find existing verification comment on the PR
/// Returns the comment ID if found
async fn find_existing_verification_comment(
    github: &GitHubAPI,
    owner: &str,
    repo: &str,
    pr_number: u64,
) -> Result<Option<i64>, Error> {
    // List all comments on the PR
    let comments = github
        .issues
        .list_comments(owner, repo, pr_number as i64)
        .send()
        .await
        .map_err(|e| Error::GpgError(format!("Failed to list PR comments: {e}")))?;

    // Look for a comment containing our signature header
    for comment in comments {
        if let Some(body) = &comment.body {
            if body.contains("## ❌ Commit Signature Verification")
                || body.contains("## ✅ Commit Signature Verification")
            {
                return Ok(Some(comment.id));
            }
        }
    }

    Ok(None)
}

/// Initialize GitHub API client from GITHUB_TOKEN env var
fn initialize_github_client_from_env() -> Result<GitHubAPI, Error> {
    let github_token = env::var("GITHUB_TOKEN")
        .map_err(|_| Error::GpgError("GITHUB_TOKEN environment variable not set".to_string()))?;

    let pat = PersonalAccessToken::new(github_token);
    let config = APIConfig::with_token(pat).shared();
    Ok(GitHubAPI::new(&config))
}

/// Initialize GitHub API client with provided token or fallback
fn initialize_github_client_with_token(
    token: Option<&str>,
    error_msg: &str,
) -> Result<GitHubAPI, Error> {
    let github_token = token
        .map(String::from)
        .ok_or_else(|| Error::GpgError(error_msg.to_string()))?;

    let pat = PersonalAccessToken::new(github_token);
    let config = APIConfig::with_token(pat).shared();
    Ok(GitHubAPI::new(&config))
}

/// Detect repository owner and name from git config or CLI args
fn detect_repository(
    git_repo: &git2::Repository,
    repo_owner: &Option<String>,
    repo_name: &Option<String>,
) -> Result<(String, String), Error> {
    if let (Some(o), Some(r)) = (repo_owner, repo_name) {
        return Ok((o.clone(), r.clone()));
    }

    // Try to auto-detect from git remote
    let remote = git_repo.find_remote("origin")?;
    let url = remote
        .url()
        .ok_or_else(|| Error::GitError("No URL for origin remote".to_string()))?;

    let (parsed_owner, parsed_repo) = parse_github_url(url)?;

    Ok((
        repo_owner.clone().unwrap_or(parsed_owner),
        repo_name.clone().unwrap_or(parsed_repo),
    ))
}

/// Display verification results for each commit
fn display_results(results: &[crate::ops::signature_ops::VerificationResult]) {
    for result in results {
        let sha_short = &result.commit.sha[..8.min(result.commit.sha.len())];
        let status_symbol = if result.passed {
            "✓".green().bold().to_string()
        } else {
            "✗".red().bold().to_string()
        };

        println!(
            "{} {}   {} {}",
            status_symbol,
            if result.passed { "OK" } else { "FAIL" },
            sha_short.bold(),
            result.commit.subject
        );

        // Show reason (privacy-safe, no PII)
        println!("    {}", result.reason.display_message());
        println!();
    }
}

/// Display verification summary
fn display_summary(
    summary: &crate::ops::signature_ops::VerificationSummary,
    results: &[crate::ops::signature_ops::VerificationResult],
) {
    use crate::ops::signature_ops::VerificationReason;

    println!("{}\n", "=== Verification Summary ===".bold());
    println!("Commits checked:         {}", summary.commits_checked);
    println!(
        "Trusted verified:        {}",
        summary.trusted_verified.to_string().green()
    );
    println!(
        "External contributors:   {}",
        summary.external_contributors.to_string().green()
    );

    if summary.failures > 0 {
        println!(
            "Failures:                {}\n",
            summary.failures.to_string().red().bold()
        );
        println!("{}", "✗ Signature verification FAILED!".red().bold());
        println!("\n{}:", "Action required".bold());
        println!("  - Review failed commits immediately");
        println!("  - Verify the committer's identity");
        println!("  - Do NOT merge if impersonation is suspected\n");
    } else {
        println!();
        println!("{}", "✓ All signature checks passed!".green().bold());
        println!("\nNo impersonation attempts detected.\n");
    }

    // List external contributors with their identities
    let external: Vec<_> = results
        .iter()
        .filter(|r| {
            matches!(
                r.reason,
                VerificationReason::ExternalUnsigned | VerificationReason::ExternalSigned
            )
        })
        .collect();

    if !external.is_empty() {
        println!("{}", "External Contributors:".bold());
        for result in external {
            let sha_short = &result.commit.sha[..8.min(result.commit.sha.len())];
            println!("  {} {}", sha_short.bold(), result.commit.author_email);
        }
        println!();
    }
}

/// Parse GitHub URL to extract owner and repo
/// Supports both HTTPS and SSH formats
fn parse_github_url(url: &str) -> Result<(String, String), Error> {
    // Remove .git suffix if present
    let url = url.trim_end_matches(".git");

    if url.starts_with("https://github.com/") {
        // HTTPS format: https://github.com/owner/repo
        let parts: Vec<&str> = url
            .trim_start_matches("https://github.com/")
            .split('/')
            .collect();
        if parts.len() >= 2 {
            return Ok((parts[0].to_string(), parts[1].to_string()));
        }
    } else if url.starts_with("git@github.com:") {
        // SSH format: git@github.com:owner/repo
        let parts: Vec<&str> = url
            .trim_start_matches("git@github.com:")
            .split('/')
            .collect();
        if parts.len() >= 2 {
            return Ok((parts[0].to_string(), parts[1].to_string()));
        }
    }

    Err(Error::GitError(format!(
        "Unable to parse GitHub URL: {url}"
    )))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ops::signature_ops::{
        CommitInfo, SignatureStatus, VerificationReason, VerificationResult, VerificationSummary,
    };

    fn make_external_result(sha: &str, email: &str) -> VerificationResult {
        VerificationResult {
            commit: CommitInfo {
                sha: sha.to_string(),
                author_email: email.to_string(),
                author_name: "External User".to_string(),
                subject: "external commit".to_string(),
                signature_status: SignatureStatus::None,
                key_id: None,
                signer: None,
            },
            passed: true,
            reason: VerificationReason::ExternalUnsigned,
        }
    }

    /// RED: issue #862 — success comment must include "External Contributors" section
    /// when there are external contributor results
    #[test]
    fn test_build_comment_includes_external_contributors_section() {
        let results = vec![make_external_result(
            "a1b2c3d4e5f6",
            "renovate[bot]@users.noreply.github.com",
        )];
        let summary = VerificationSummary {
            commits_checked: 1,
            trusted_verified: 0,
            external_contributors: 1,
            failures: 0,
        };

        let comment = build_comment(&results, &summary).unwrap();
        assert!(
            comment.contains("External Contributors"),
            "Comment should contain 'External Contributors' section, got:\n{comment}"
        );
    }

    /// RED: issue #862 — success comment must include author email of external contributor
    #[test]
    fn test_build_comment_includes_external_contributor_email() {
        let results = vec![make_external_result(
            "a1b2c3d4e5f6",
            "renovate[bot]@users.noreply.github.com",
        )];
        let summary = VerificationSummary {
            commits_checked: 1,
            trusted_verified: 0,
            external_contributors: 1,
            failures: 0,
        };

        let comment = build_comment(&results, &summary).unwrap();
        assert!(
            comment.contains("renovate[bot]@users.noreply.github.com"),
            "Comment should include external contributor email, got:\n{comment}"
        );
    }

    /// RED: issue #862 — success comment must include short SHA of external contributor commit
    #[test]
    fn test_build_comment_includes_external_contributor_sha() {
        let results = vec![make_external_result(
            "a1b2c3d4e5f6",
            "renovate[bot]@users.noreply.github.com",
        )];
        let summary = VerificationSummary {
            commits_checked: 1,
            trusted_verified: 0,
            external_contributors: 1,
            failures: 0,
        };

        let comment = build_comment(&results, &summary).unwrap();
        // short SHA = first 8 chars
        assert!(
            comment.contains("a1b2c3d4"),
            "Comment should include short SHA of external contributor, got:\n{comment}"
        );
    }

    /// Verify that the comment does NOT include external contributor section
    /// when there are no external contributors (regression guard)
    #[test]
    fn test_build_comment_no_external_contributors_no_section() {
        let results = vec![];
        let summary = VerificationSummary {
            commits_checked: 0,
            trusted_verified: 0,
            external_contributors: 0,
            failures: 0,
        };

        let comment = build_comment(&results, &summary).unwrap();
        assert!(
            !comment.contains("### External Contributors"),
            "Comment should not contain external contributors section when there are none, got:\n{comment}"
        );
    }
}
