use serde::{Deserialize, Serialize};
use std::time::Instant;
use tokio::time::{sleep, Duration};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrackerField {
    pub field_id: i64,
    pub label: String,
    pub field_type: String,
    pub values: Vec<FieldValue>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FieldValue {
    pub id: i64,
    pub label: String,
}

pub struct TuleapClient {
    http: reqwest::Client,
    base_url: String,
    username: String,
    password: String,
}

impl TuleapClient {
    pub fn new(http: &reqwest::Client, base_url: &str, username: &str, password: &str) -> Self {
        Self {
            http: http.clone(),
            base_url: base_url.trim_end_matches('/').to_string(),
            username: username.to_string(),
            password: password.to_string(),
        }
    }

    async fn send_get(&self, url: &str) -> Result<reqwest::Response, String> {
        const MAX_ATTEMPTS: u32 = 3;
        const BASE_DELAY_MS: u64 = 500;

        for attempt in 1..=MAX_ATTEMPTS {
            let started_at = Instant::now();
            eprintln!("[tuleap] -> GET {} (attempt {}/{})", url, attempt, MAX_ATTEMPTS);

            let response = self
                .http
                .get(url)
                .basic_auth(&self.username, Some(&self.password))
                .send()
                .await;

            match response {
                Ok(resp) => {
                    let status = resp.status();
                    eprintln!(
                        "[tuleap] <- GET {} | status={} | {}ms",
                        url,
                        status,
                        started_at.elapsed().as_millis()
                    );

                    // Retry transient HTTP failures.
                    if (status == reqwest::StatusCode::TOO_MANY_REQUESTS || status.is_server_error())
                        && attempt < MAX_ATTEMPTS
                    {
                        let delay_ms = BASE_DELAY_MS * 2u64.pow(attempt - 1);
                        eprintln!(
                            "[tuleap] ~~ retry GET {} in {}ms (status={})",
                            url, delay_ms, status
                        );
                        sleep(Duration::from_millis(delay_ms)).await;
                        continue;
                    }

                    return Ok(resp);
                }
                Err(err) => {
                    eprintln!(
                        "[tuleap] xx GET {} | error={} | {}ms",
                        url,
                        err,
                        started_at.elapsed().as_millis()
                    );

                    if attempt < MAX_ATTEMPTS {
                        let delay_ms = BASE_DELAY_MS * 2u64.pow(attempt - 1);
                        eprintln!(
                            "[tuleap] ~~ retry GET {} in {}ms (error={})",
                            url, delay_ms, err
                        );
                        sleep(Duration::from_millis(delay_ms)).await;
                        continue;
                    }

                    return Err(format!("request failed: {}", err));
                }
            }
        }

        Err("request failed after retries".to_string())
    }

    #[allow(dead_code)]
    pub async fn test_connection(&self) -> Result<(), String> {
        let url = format!("{}/api/projects?limit=1", self.base_url);
        let resp = self.send_get(&url).await?;

        if resp.status().is_success() {
            Ok(())
        } else {
            Err(format!("connection test failed: HTTP {}", resp.status()))
        }
    }

    pub async fn get_tracker_fields(&self, tracker_id: i32) -> Result<Vec<TrackerField>, String> {
        let url = format!("{}/api/trackers/{}", self.base_url, tracker_id);
        let resp = self.send_get(&url).await?;

        if !resp.status().is_success() {
            return Err(format!("get tracker fields failed: HTTP {}", resp.status()));
        }

        let body: serde_json::Value = resp
            .json()
            .await
            .map_err(|e| format!("failed to parse response: {}", e))?;

        Ok(parse_tracker_fields(&body))
    }

    pub async fn get_artifacts(&self, tracker_id: i32) -> Result<Vec<serde_json::Value>, String> {
        let mut all_artifacts: Vec<serde_json::Value> = Vec::new();
        let mut offset = 0usize;
        let limit = 50usize;

        loop {
            let url = format!(
                "{}/api/trackers/{}/artifacts?values=all&limit={}&offset={}",
                self.base_url, tracker_id, limit, offset
            );
            let resp = self.send_get(&url).await?;

            if !resp.status().is_success() {
                return Err(format!("get artifacts failed: HTTP {}", resp.status()));
            }

            // Read total size from header before consuming body
            let total: usize = resp
                .headers()
                .get("x-pagination-size")
                .and_then(|v| v.to_str().ok())
                .and_then(|s| s.parse().ok())
                .unwrap_or(0);

            let body: serde_json::Value = resp
                .json()
                .await
                .map_err(|e| format!("failed to parse response: {}", e))?;

            let artifacts = body
                .as_array()
                .cloned()
                .unwrap_or_default();

            let fetched = artifacts.len();
            all_artifacts.extend(artifacts);

            offset += fetched;

            if fetched == 0 || offset >= total {
                break;
            }
        }

        Ok(all_artifacts)
    }
}

// ---------------------------------------------------------------------------
// Pure parsing functions (unit-testable without HTTP)
// ---------------------------------------------------------------------------

pub fn parse_tracker_fields(tracker_json: &serde_json::Value) -> Vec<TrackerField> {
    let fields = match tracker_json.get("fields").and_then(|f| f.as_array()) {
        Some(f) => f,
        None => return vec![],
    };

    fields
        .iter()
        .filter_map(|field| {
            let field_id = field.get("field_id")?.as_i64()?;
            let label = field.get("label")?.as_str()?.to_string();
            let field_type = field.get("type")?.as_str()?.to_string();

            let values = match field_type.as_str() {
                "sb" | "msb" | "rb" | "cb" => extract_field_values(field),
                _ => vec![],
            };

            Some(TrackerField {
                field_id,
                label,
                field_type,
                values,
            })
        })
        .collect()
}

fn extract_field_values(field: &serde_json::Value) -> Vec<FieldValue> {
    // Try "values" first (sb, rb), then "bind_value_objects" (msb)
    let candidates = field
        .get("values")
        .and_then(|v| v.as_array())
        .filter(|arr| !arr.is_empty())
        .or_else(|| {
            field
                .get("bind_value_objects")
                .and_then(|v| v.as_array())
        });

    let arr = match candidates {
        Some(a) => a,
        None => return vec![],
    };

    arr.iter()
        .filter_map(|v| {
            let id = v.get("id")?.as_i64()?;
            let label = v
                .get("label")
                .and_then(|l| l.as_str())
                .unwrap_or("")
                .to_string();
            // Filter out "None" labels
            if label == "None" {
                return None;
            }
            Some(FieldValue { id, label })
        })
        .collect()
}

pub fn extract_artifact_field_values(
    artifact: &serde_json::Value,
    field_label: &str,
) -> Vec<String> {
    fn push_unique(values: &mut Vec<String>, value: String) {
        if value.is_empty() {
            return;
        }
        if !values.iter().any(|v| v == &value) {
            values.push(value);
        }
    }

    let values_arr = match artifact.get("values").and_then(|v| v.as_array()) {
        Some(a) => a,
        None => return vec![],
    };

    // Find the field entry matching the label
    let field_entry = values_arr
        .iter()
        .find(|entry| entry.get("label").and_then(|l| l.as_str()) == Some(field_label));

    let entry = match field_entry {
        Some(e) => e,
        None => return vec![],
    };

    let field_type = entry
        .get("type")
        .and_then(|t| t.as_str())
        .unwrap_or("");

    match field_type {
        "sb" | "rb" => {
            // values[*].id + values[*].label
            entry
                .get("values")
                .and_then(|v| v.as_array())
                .map(|arr| {
                    let mut out = Vec::new();
                    for v in arr {
                        if let Some(id) = v.get("id") {
                            match id {
                                serde_json::Value::String(s) => push_unique(&mut out, s.clone()),
                                serde_json::Value::Number(n) => push_unique(&mut out, n.to_string()),
                                _ => {}
                            }
                        }
                        if let Some(label) = v.get("label").and_then(|l| l.as_str()) {
                            push_unique(&mut out, label.to_string());
                        }
                    }
                    out
                })
                .unwrap_or_default()
        }
        "msb" | "cb" => {
            // bind_value_objects[*].id + display_name/label
            entry
                .get("bind_value_objects")
                .and_then(|v| v.as_array())
                .map(|arr| {
                    let mut out = Vec::new();
                    for v in arr {
                        if let Some(id) = v.get("id") {
                            match id {
                                serde_json::Value::String(s) => push_unique(&mut out, s.clone()),
                                serde_json::Value::Number(n) => push_unique(&mut out, n.to_string()),
                                _ => {}
                            }
                        }
                        if let Some(label) = v
                            .get("display_name")
                            .and_then(|d| d.as_str())
                            .or_else(|| v.get("label").and_then(|l| l.as_str()))
                        {
                            push_unique(&mut out, label.to_string());
                        }
                    }
                    out
                })
                .unwrap_or_default()
        }
        "string" | "text" | "int" | "float" => {
            // scalar "value" field
            entry
                .get("value")
                .map(|v| match v {
                    serde_json::Value::String(s) => vec![s.clone()],
                    serde_json::Value::Number(n) => vec![n.to_string()],
                    serde_json::Value::Bool(b) => vec![b.to_string()],
                    serde_json::Value::Null => vec![],
                    other => vec![other.to_string()],
                })
                .unwrap_or_default()
        }
        _ => vec![],
    }
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn test_parse_tracker_fields_extracts_sb() {
        let tracker = json!({
            "fields": [
                {
                    "field_id": 1,
                    "label": "Status",
                    "type": "sb",
                    "values": [
                        { "id": 0, "label": "None" },
                        { "id": 1, "label": "Nouveau" },
                        { "id": 2, "label": "En cours" }
                    ]
                }
            ]
        });

        let fields = parse_tracker_fields(&tracker);
        assert_eq!(fields.len(), 1);
        let f = &fields[0];
        assert_eq!(f.label, "Status");
        assert_eq!(f.field_type, "sb");
        // "None" is filtered out
        assert_eq!(f.values.len(), 2);
        assert_eq!(f.values[0].label, "Nouveau");
        assert_eq!(f.values[1].label, "En cours");
    }

    #[test]
    fn test_parse_tracker_fields_extracts_msb() {
        let tracker = json!({
            "fields": [
                {
                    "field_id": 2,
                    "label": "Assigned to",
                    "type": "msb",
                    "bind_value_objects": [
                        { "id": 10, "label": "Alice" },
                        { "id": 11, "label": "Bob" }
                    ]
                }
            ]
        });

        let fields = parse_tracker_fields(&tracker);
        assert_eq!(fields.len(), 1);
        let f = &fields[0];
        assert_eq!(f.label, "Assigned to");
        assert_eq!(f.values.len(), 2);
        assert_eq!(f.values[0].label, "Alice");
        assert_eq!(f.values[1].label, "Bob");
    }

    #[test]
    fn test_parse_tracker_fields_skips_text_fields() {
        let tracker = json!({
            "fields": [
                {
                    "field_id": 3,
                    "label": "Summary",
                    "type": "text"
                }
            ]
        });

        let fields = parse_tracker_fields(&tracker);
        assert_eq!(fields.len(), 1);
        let f = &fields[0];
        assert_eq!(f.label, "Summary");
        assert_eq!(f.field_type, "text");
        assert!(f.values.is_empty());
    }

    #[test]
    fn test_extract_artifact_field_values_sb() {
        let artifact = json!({
            "values": [
                {
                    "field_id": 1,
                    "label": "Status",
                    "type": "sb",
                    "values": [
                        { "id": 1, "label": "Nouveau" }
                    ]
                }
            ]
        });

        let result = extract_artifact_field_values(&artifact, "Status");
        assert_eq!(result, vec!["1", "Nouveau"]);
    }

    #[test]
    fn test_extract_artifact_field_values_msb() {
        let artifact = json!({
            "values": [
                {
                    "field_id": 2,
                    "label": "Assigned to",
                    "type": "msb",
                    "bind_value_objects": [
                        { "id": 10, "display_name": "Team Maintenance" }
                    ]
                }
            ]
        });

        let result = extract_artifact_field_values(&artifact, "Assigned to");
        assert_eq!(result, vec!["10", "Team Maintenance"]);
    }

    #[test]
    fn test_extract_artifact_field_values_missing_field() {
        let artifact = json!({
            "values": []
        });

        let result = extract_artifact_field_values(&artifact, "Status");
        assert!(result.is_empty());
    }

    #[test]
    fn test_extract_artifact_field_values_string_field() {
        let artifact = json!({
            "values": [
                {
                    "field_id": 5,
                    "label": "Summary",
                    "type": "string",
                    "value": "Login broken"
                }
            ]
        });

        let result = extract_artifact_field_values(&artifact, "Summary");
        assert_eq!(result, vec!["Login broken"]);
    }
}
