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

pub struct GiteaTracker {
    base_url: String,
    http: reqwest::Client,
    token: SecretString,
}

impl GiteaTracker {
    pub fn new(base_url: String, token: SecretString) -> Self {
        Self {
            base_url: base_url.trim_end_matches('/').to_string(),
            http: reqwest::Client::new(),
            token,
        }
    }

    fn api_url(&self, path: &str) -> String {
        format!("{}/api/v1{}", self.base_url, path)
    }
}

impl IssueTracker for GiteaTracker {
    fn name(&self) -> &str {
        "gitea"
    }

    async fn create_issue(
        &self,
        owner: &str,
        repo: &str,
        title: &str,
        body: &str,
        labels: &[String],
    ) -> Result<TrackerIssue, CoreError> {
        let url = self.api_url(&format!("/repos/{owner}/{repo}/issues"));

        // Gitea expects label IDs (integers), not names. Append label names
        // to the body instead since resolving IDs would require extra API calls.
        let mut full_body = body.to_string();
        if !labels.is_empty() {
            full_body.push_str("\n\n**Labels:** ");
            full_body.push_str(&labels.join(", "));
        }
        let payload = serde_json::json!({
            "title": title,
            "body": full_body,
        });

        let resp = self
            .http
            .post(&url)
            .header(
                "Authorization",
                format!("token {}", self.token.expose_secret()),
            )
            .json(&payload)
            .send()
            .await
            .map_err(|e| CoreError::IssueTracker(format!("Gitea create issue failed: {e}")))?;

        if !resp.status().is_success() {
            let status = resp.status();
            let text = resp.text().await.unwrap_or_default();
            return Err(CoreError::IssueTracker(format!(
                "Gitea returned {status}: {text}"
            )));
        }

        let issue: serde_json::Value = resp
            .json()
            .await
            .map_err(|e| CoreError::IssueTracker(format!("Failed to parse Gitea response: {e}")))?;

        Ok(TrackerIssue::new(
            String::new(),
            TrackerType::Gitea,
            issue["number"].to_string(),
            issue["html_url"].as_str().unwrap_or("").to_string(),
            title.to_string(),
        ))
    }

    async fn update_issue_status(
        &self,
        owner: &str,
        repo: &str,
        external_id: &str,
        status: &str,
    ) -> Result<(), CoreError> {
        let url = self.api_url(&format!("/repos/{owner}/{repo}/issues/{external_id}"));

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

        let resp = self
            .http
            .patch(&url)
            .header(
                "Authorization",
                format!("token {}", self.token.expose_secret()),
            )
            .json(&serde_json::json!({ "state": state }))
            .send()
            .await
            .map_err(|e| CoreError::IssueTracker(format!("Gitea update issue failed: {e}")))?;

        if !resp.status().is_success() {
            let status = resp.status();
            let text = resp.text().await.unwrap_or_default();
            return Err(CoreError::IssueTracker(format!(
                "Gitea update issue returned {status}: {text}"
            )));
        }

        Ok(())
    }

    async fn add_comment(
        &self,
        owner: &str,
        repo: &str,
        external_id: &str,
        body: &str,
    ) -> Result<(), CoreError> {
        let url = self.api_url(&format!(
            "/repos/{owner}/{repo}/issues/{external_id}/comments"
        ));

        let resp = self
            .http
            .post(&url)
            .header(
                "Authorization",
                format!("token {}", self.token.expose_secret()),
            )
            .json(&serde_json::json!({ "body": body }))
            .send()
            .await
            .map_err(|e| CoreError::IssueTracker(format!("Gitea add comment failed: {e}")))?;

        if !resp.status().is_success() {
            let status = resp.status();
            let text = resp.text().await.unwrap_or_default();
            return Err(CoreError::IssueTracker(format!(
                "Gitea add comment returned {status}: {text}"
            )));
        }

        Ok(())
    }

    async fn create_pr_review(
        &self,
        owner: &str,
        repo: &str,
        pr_number: u64,
        body: &str,
        comments: Vec<ReviewComment>,
    ) -> Result<(), CoreError> {
        let url = self.api_url(&format!("/repos/{owner}/{repo}/pulls/{pr_number}/reviews"));

        let review_comments: Vec<serde_json::Value> = comments
            .iter()
            .map(|c| {
                serde_json::json!({
                    "path": c.path,
                    "new_position": c.line,
                    "body": c.body,
                })
            })
            .collect();

        let resp = self
            .http
            .post(&url)
            .header(
                "Authorization",
                format!("token {}", self.token.expose_secret()),
            )
            .json(&serde_json::json!({
                "body": body,
                "event": "COMMENT",
                "comments": review_comments,
            }))
            .send()
            .await
            .map_err(|e| CoreError::IssueTracker(format!("Gitea PR review failed: {e}")))?;

        if !resp.status().is_success() {
            let status = resp.status();
            let text = resp.text().await.unwrap_or_default();

            // If inline comments caused the failure, retry with just the summary body
            if !comments.is_empty() {
                tracing::warn!(
                    "Gitea PR review with inline comments failed ({status}): {text}, retrying as plain comment"
                );
                let fallback_url = self.api_url(&format!(
                    "/repos/{owner}/{repo}/issues/{pr_number}/comments"
                ));
                let fallback_resp = self
                    .http
                    .post(&fallback_url)
                    .header(
                        "Authorization",
                        format!("token {}", self.token.expose_secret()),
                    )
                    .json(&serde_json::json!({ "body": body }))
                    .send()
                    .await
                    .map_err(|e| {
                        CoreError::IssueTracker(format!("Gitea PR comment fallback failed: {e}"))
                    })?;

                if !fallback_resp.status().is_success() {
                    let fb_status = fallback_resp.status();
                    let fb_text = fallback_resp.text().await.unwrap_or_default();
                    return Err(CoreError::IssueTracker(format!(
                        "Gitea PR comment fallback returned {fb_status}: {fb_text}"
                    )));
                }

                return Ok(());
            }

            return Err(CoreError::IssueTracker(format!(
                "Gitea PR review returned {status}: {text}"
            )));
        }

        Ok(())
    }

    async fn find_existing_issue(
        &self,
        owner: &str,
        repo: &str,
        fingerprint: &str,
    ) -> Result<Option<TrackerIssue>, CoreError> {
        let url = self.api_url(&format!(
            "/repos/{owner}/{repo}/issues?type=issues&state=all&q={fingerprint}"
        ));

        let resp = self
            .http
            .get(&url)
            .header(
                "Authorization",
                format!("token {}", self.token.expose_secret()),
            )
            .send()
            .await
            .map_err(|e| CoreError::IssueTracker(format!("Gitea search failed: {e}")))?;

        let issues: Vec<serde_json::Value> = resp.json().await.unwrap_or_default();
        if let Some(issue) = issues.first() {
            Ok(Some(TrackerIssue::new(
                String::new(),
                TrackerType::Gitea,
                issue["number"].to_string(),
                issue["html_url"].as_str().unwrap_or("").to_string(),
                issue["title"].as_str().unwrap_or("").to_string(),
            )))
        } else {
            Ok(None)
        }
    }
}
