use mongodb::bson::doc;

use crate::pipeline::repo_view::RepoView;
use compliance_core::models::*;

use super::orchestrator::{extract_base_url, PipelineOrchestrator};
use super::tracker_dispatch::TrackerDispatch;
use crate::error::AgentError;
use crate::trackers;

impl PipelineOrchestrator {
    /// Build an issue tracker client from a repository's tracker configuration.
    /// Returns `None` if the repo has no tracker configured.
    pub(super) fn build_tracker(&self, repo: &RepoView) -> Option<TrackerDispatch> {
        let tracker_type = repo.tracker_type.as_ref()?;
        // Per-repo token takes precedence, fall back to global config
        match tracker_type {
            TrackerType::GitHub => {
                let token = repo.tracker_token.clone().or_else(|| {
                    self.config.github_token.as_ref().map(|t| {
                        use secrecy::ExposeSecret;
                        t.expose_secret().to_string()
                    })
                })?;
                let secret = secrecy::SecretString::from(token);
                match trackers::github::GitHubTracker::new(&secret) {
                    Ok(t) => Some(TrackerDispatch::GitHub(t)),
                    Err(e) => {
                        tracing::warn!("Failed to build GitHub tracker: {e}");
                        None
                    }
                }
            }
            TrackerType::GitLab => {
                let base_url = self
                    .config
                    .gitlab_url
                    .clone()
                    .unwrap_or_else(|| "https://gitlab.com".to_string());
                let token = repo.tracker_token.clone().or_else(|| {
                    self.config.gitlab_token.as_ref().map(|t| {
                        use secrecy::ExposeSecret;
                        t.expose_secret().to_string()
                    })
                })?;
                let secret = secrecy::SecretString::from(token);
                Some(TrackerDispatch::GitLab(
                    trackers::gitlab::GitLabTracker::new(base_url, secret),
                ))
            }
            TrackerType::Gitea => {
                let token = repo.tracker_token.clone()?;
                let base_url = extract_base_url(&repo.git_url)?;
                let secret = secrecy::SecretString::from(token);
                Some(TrackerDispatch::Gitea(trackers::gitea::GiteaTracker::new(
                    base_url, secret,
                )))
            }
            TrackerType::Jira => {
                let base_url = self.config.jira_url.clone()?;
                let email = self.config.jira_email.clone()?;
                let project_key = self.config.jira_project_key.clone()?;
                let token = repo.tracker_token.clone().or_else(|| {
                    self.config.jira_api_token.as_ref().map(|t| {
                        use secrecy::ExposeSecret;
                        t.expose_secret().to_string()
                    })
                })?;
                let secret = secrecy::SecretString::from(token);
                Some(TrackerDispatch::Jira(trackers::jira::JiraTracker::new(
                    base_url,
                    email,
                    secret,
                    project_key,
                )))
            }
        }
    }

    /// Create tracker issues for new findings (severity >= Medium).
    /// Checks for duplicates via fingerprint search before creating.
    #[tracing::instrument(skip_all, fields(repo_id = %repo_id))]
    pub(super) async fn create_tracker_issues(
        &self,
        repo: &RepoView,
        repo_id: &str,
        new_findings: &[Finding],
    ) -> Result<(), AgentError> {
        let tracker = match self.build_tracker(repo) {
            Some(t) => t,
            None => {
                tracing::info!("[{repo_id}] No issue tracker configured, skipping");
                return Ok(());
            }
        };

        let owner = match repo.tracker_owner.as_deref() {
            Some(o) => o,
            None => {
                tracing::warn!("[{repo_id}] tracker_owner not set, skipping issue creation");
                return Ok(());
            }
        };
        let tracker_repo_name = match repo.tracker_repo.as_deref() {
            Some(r) => r,
            None => {
                tracing::warn!("[{repo_id}] tracker_repo not set, skipping issue creation");
                return Ok(());
            }
        };

        // Only create issues for medium+ severity findings
        let actionable: Vec<&Finding> = new_findings
            .iter()
            .filter(|f| {
                matches!(
                    f.severity,
                    Severity::Medium | Severity::High | Severity::Critical
                )
            })
            .collect();

        if actionable.is_empty() {
            tracing::info!("[{repo_id}] No medium+ findings, skipping issue creation");
            return Ok(());
        }

        tracing::info!(
            "[{repo_id}] Creating issues for {} findings via {}",
            actionable.len(),
            tracker.name()
        );

        let mut created = 0u32;
        for finding in actionable {
            let title = format!(
                "[{}] {}: {}",
                finding.severity, finding.scanner, finding.title
            );

            // Check if an issue already exists by fingerprint first, then by title
            let mut found_existing = false;
            for search_term in [&finding.fingerprint, &title] {
                match tracker
                    .find_existing_issue(owner, tracker_repo_name, search_term)
                    .await
                {
                    Ok(Some(existing)) => {
                        tracing::debug!(
                            "[{repo_id}] Issue already exists for '{}': {}",
                            search_term,
                            existing.external_url
                        );
                        found_existing = true;
                        break;
                    }
                    Ok(None) => {}
                    Err(e) => {
                        tracing::warn!("[{repo_id}] Failed to search for existing issue: {e}");
                    }
                }
            }
            if found_existing {
                continue;
            }
            let body = format_issue_body(finding);
            let labels = vec![
                format!("severity:{}", finding.severity),
                format!("scanner:{}", finding.scanner),
                "compliance-scanner".to_string(),
            ];

            match tracker
                .create_issue(owner, tracker_repo_name, &title, &body, &labels)
                .await
            {
                Ok(mut issue) => {
                    issue.finding_id = finding
                        .id
                        .as_ref()
                        .map(|id| id.to_hex())
                        .unwrap_or_default();

                    // Update the finding with the issue URL
                    if let Some(finding_id) = &finding.id {
                        let _ = self
                            .db
                            .findings()
                            .update_one(
                                doc! { "_id": finding_id },
                                doc! { "$set": { "tracker_issue_url": &issue.external_url } },
                            )
                            .await;
                    }

                    // Store the tracker issue record
                    if let Err(e) = self.db.tracker_issues().insert_one(&issue).await {
                        tracing::warn!("[{repo_id}] Failed to store tracker issue: {e}");
                    }

                    created += 1;
                }
                Err(e) => {
                    tracing::warn!(
                        "[{repo_id}] Failed to create issue for {}: {e}",
                        finding.fingerprint
                    );
                }
            }
        }

        tracing::info!("[{repo_id}] Created {created} tracker issues");
        Ok(())
    }
}

/// Format a finding into a markdown issue body for the tracker.
pub(super) fn format_issue_body(finding: &Finding) -> String {
    let mut body = String::new();

    body.push_str(&format!("## {} Finding\n\n", finding.severity));
    body.push_str(&format!("**Scanner:** {}\n", finding.scanner));
    body.push_str(&format!("**Severity:** {}\n", finding.severity));

    if let Some(rule) = &finding.rule_id {
        body.push_str(&format!("**Rule:** {}\n", rule));
    }
    if let Some(cwe) = &finding.cwe {
        body.push_str(&format!("**CWE:** {}\n", cwe));
    }

    body.push_str(&format!("\n### Description\n\n{}\n", finding.description));

    if let Some(file_path) = &finding.file_path {
        body.push_str(&format!("\n### Location\n\n**File:** `{}`", file_path));
        if let Some(line) = finding.line_number {
            body.push_str(&format!(" (line {})", line));
        }
        body.push('\n');
    }

    if let Some(snippet) = &finding.code_snippet {
        body.push_str(&format!("\n### Code\n\n```\n{}\n```\n", snippet));
    }

    if let Some(remediation) = &finding.remediation {
        body.push_str(&format!("\n### Remediation\n\n{}\n", remediation));
    }

    if let Some(fix) = &finding.suggested_fix {
        body.push_str(&format!("\n### Suggested Fix\n\n```\n{}\n```\n", fix));
    }

    body.push_str(&format!(
        "\n---\n*Fingerprint:* `{}`\n*Generated by compliance-scanner*",
        finding.fingerprint
    ));

    body
}
