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 GitLabTracker {
    base_url: String,
    http: reqwest::Client,
    token: SecretString,
}

impl GitLabTracker {
    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/v4{}", self.base_url, path)
    }

    fn project_path(owner: &str, repo: &str) -> String {
        urlencoding::encode(&format!("{owner}/{repo}")).to_string()
    }
}

impl IssueTracker for GitLabTracker {
    fn name(&self) -> &str {
        "gitlab"
    }

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

        let mut payload = serde_json::json!({
            "title": title,
            "description": body,
        });
        if !labels.is_empty() {
            payload["labels"] = serde_json::Value::String(labels.join(","));
        }

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

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

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

        Ok(TrackerIssue::new(
            String::new(),
            TrackerType::GitLab,
            issue["iid"].to_string(),
            issue["web_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 project = Self::project_path(owner, repo);
        let url = self.api_url(&format!("/projects/{project}/issues/{external_id}"));

        let state_event = match status {
            "closed" | "resolved" => "close",
            _ => "reopen",
        };

        self.http
            .put(&url)
            .header("PRIVATE-TOKEN", self.token.expose_secret())
            .json(&serde_json::json!({ "state_event": state_event }))
            .send()
            .await
            .map_err(|e| CoreError::IssueTracker(format!("GitLab update issue failed: {e}")))?;

        Ok(())
    }

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

        self.http
            .post(&url)
            .header("PRIVATE-TOKEN", self.token.expose_secret())
            .json(&serde_json::json!({ "body": body }))
            .send()
            .await
            .map_err(|e| CoreError::IssueTracker(format!("GitLab 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 project = Self::project_path(owner, repo);

        // Post overall review as MR note
        let note_url = self.api_url(&format!(
            "/projects/{project}/merge_requests/{pr_number}/notes"
        ));
        self.http
            .post(&note_url)
            .header("PRIVATE-TOKEN", self.token.expose_secret())
            .json(&serde_json::json!({ "body": body }))
            .send()
            .await
            .map_err(|e| CoreError::IssueTracker(format!("GitLab MR note failed: {e}")))?;

        // Post individual line comments as MR discussions
        for comment in comments {
            let disc_url = self.api_url(&format!(
                "/projects/{project}/merge_requests/{pr_number}/discussions"
            ));
            let payload = serde_json::json!({
                "body": comment.body,
                "position": {
                    "position_type": "text",
                    "new_path": comment.path,
                    "new_line": comment.line,
                }
            });
            let _ = self
                .http
                .post(&disc_url)
                .header("PRIVATE-TOKEN", self.token.expose_secret())
                .json(&payload)
                .send()
                .await;
        }

        Ok(())
    }

    async fn find_existing_issue(
        &self,
        owner: &str,
        repo: &str,
        fingerprint: &str,
    ) -> Result<Option<TrackerIssue>, CoreError> {
        let project = Self::project_path(owner, repo);
        let url = self.api_url(&format!("/projects/{project}/issues?search={fingerprint}"));

        let resp = self
            .http
            .get(&url)
            .header("PRIVATE-TOKEN", self.token.expose_secret())
            .send()
            .await
            .map_err(|e| CoreError::IssueTracker(format!("GitLab 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::GitLab,
                issue["iid"].to_string(),
                issue["web_url"].as_str().unwrap_or("").to_string(),
                issue["title"].as_str().unwrap_or("").to_string(),
            )))
        } else {
            Ok(None)
        }
    }
}
