use compliance_core::error::CoreError;
use compliance_core::models::{TrackerIssue, TrackerType};
use compliance_core::traits::issue_tracker::{IssueTracker, ReviewComment};
use octocrab::Octocrab;
use secrecy::{ExposeSecret, SecretString};

pub struct GitHubTracker {
    client: Octocrab,
}

impl GitHubTracker {
    pub fn new(token: &SecretString) -> Result<Self, CoreError> {
        let client = Octocrab::builder()
            .personal_token(token.expose_secret().to_string())
            .build()
            .map_err(|e| CoreError::IssueTracker(format!("Failed to create GitHub client: {e}")))?;
        Ok(Self { client })
    }
}

impl IssueTracker for GitHubTracker {
    fn name(&self) -> &str {
        "github"
    }

    async fn create_issue(
        &self,
        owner: &str,
        repo: &str,
        title: &str,
        body: &str,
        labels: &[String],
    ) -> Result<TrackerIssue, CoreError> {
        let issues_handler = self.client.issues(owner, repo);
        let mut builder = issues_handler.create(title).body(body);
        if !labels.is_empty() {
            builder = builder.labels(labels.to_vec());
        }
        let issue = builder
            .send()
            .await
            .map_err(|e| CoreError::IssueTracker(format!("GitHub create issue failed: {e}")))?;

        Ok(TrackerIssue::new(
            String::new(),
            TrackerType::GitHub,
            issue.number.to_string(),
            issue.html_url.to_string(),
            title.to_string(),
        ))
    }

    async fn update_issue_status(
        &self,
        owner: &str,
        repo: &str,
        external_id: &str,
        status: &str,
    ) -> Result<(), CoreError> {
        let issue_number: u64 = external_id
            .parse()
            .map_err(|_| CoreError::IssueTracker("Invalid issue number".to_string()))?;

        let state_str = match status {
            "closed" | "resolved" => "closed",
            _ => "open",
        };

        // Use the REST API directly for state update
        let route = format!("/repos/{owner}/{repo}/issues/{issue_number}");
        let body = serde_json::json!({ "state": state_str });
        let _: serde_json::Value = self
            .client
            .post(route, Some(&body))
            .await
            .map_err(|e| CoreError::IssueTracker(format!("GitHub update issue failed: {e}")))?;

        Ok(())
    }

    async fn add_comment(
        &self,
        owner: &str,
        repo: &str,
        external_id: &str,
        body: &str,
    ) -> Result<(), CoreError> {
        let issue_number: u64 = external_id
            .parse()
            .map_err(|_| CoreError::IssueTracker("Invalid issue number".to_string()))?;

        self.client
            .issues(owner, repo)
            .create_comment(issue_number, body)
            .await
            .map_err(|e| CoreError::IssueTracker(format!("GitHub add comment failed: {e}")))?;

        Ok(())
    }

    async fn create_pr_review(
        &self,
        owner: &str,
        repo: &str,
        pr_number: u64,
        body: &str,
        comments: Vec<ReviewComment>,
    ) -> Result<(), CoreError> {
        let review_comments: Vec<serde_json::Value> = comments
            .iter()
            .map(|c| {
                serde_json::json!({
                    "path": c.path,
                    "line": c.line,
                    "body": c.body,
                })
            })
            .collect();

        let review_body = serde_json::json!({
            "body": body,
            "event": "COMMENT",
            "comments": review_comments,
        });

        let route = format!("/repos/{owner}/{repo}/pulls/{pr_number}/reviews");
        let _: serde_json::Value = self
            .client
            .post(route, Some(&review_body))
            .await
            .map_err(|e| CoreError::IssueTracker(format!("GitHub PR review failed: {e}")))?;

        Ok(())
    }

    async fn find_existing_issue(
        &self,
        owner: &str,
        repo: &str,
        fingerprint: &str,
    ) -> Result<Option<TrackerIssue>, CoreError> {
        let query = format!("repo:{owner}/{repo} is:issue {fingerprint}");
        let results = self
            .client
            .search()
            .issues_and_pull_requests(&query)
            .send()
            .await
            .map_err(|e| CoreError::IssueTracker(format!("GitHub search failed: {e}")))?;

        if let Some(issue) = results.items.first() {
            Ok(Some(TrackerIssue::new(
                String::new(),
                TrackerType::GitHub,
                issue.number.to_string(),
                issue.html_url.to_string(),
                issue.title.clone(),
            )))
        } else {
            Ok(None)
        }
    }
}
