use crate::services::graylog_scoring::GraylogEvent;
use serde_json::Value;
use std::time::Instant;
use tokio::time::{sleep, Duration};

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

impl GraylogClient {
    pub fn new(http: &reqwest::Client, base_url: &str, token: &str) -> Self {
        Self {
            http: http.clone(),
            base_url: base_url.trim_end_matches('/').to_string(),
            token: token.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!(
                "[graylog] -> GET {} (attempt {}/{})",
                url, attempt, MAX_ATTEMPTS
            );

            let response = self
                .http
                .get(url)
                .header("Authorization", format!("Bearer {}", self.token))
                .header("X-Requested-By", "orchai")
                .send()
                .await;

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

                    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!(
                            "[graylog] ~~ retry GET {} in {}ms (status={})",
                            url, delay_ms, status
                        );
                        sleep(Duration::from_millis(delay_ms)).await;
                        continue;
                    }

                    return Ok(resp);
                }
                Err(err) => {
                    eprintln!(
                        "[graylog] 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!(
                            "[graylog] ~~ retry GET {} in {}ms (error={})",
                            url, delay_ms, err
                        );
                        sleep(Duration::from_millis(delay_ms)).await;
                        continue;
                    }

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

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

    pub async fn test_connection(&self) -> Result<(), String> {
        let url = format!("{}/api/system", self.base_url);
        let resp = self.send_get(&url).await?;

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

    pub async fn search_relative(
        &self,
        query: &str,
        stream_id: Option<&str>,
        range_seconds: i32,
    ) -> Result<Vec<GraylogEvent>, String> {
        let normalized_range = range_seconds.max(60);
        let mut url = format!(
            "{}/api/search/universal/relative?query={}&range={}&limit=500",
            self.base_url,
            urlencoding::encode(query),
            normalized_range
        );

        if let Some(stream) = stream_id.map(str::trim).filter(|value| !value.is_empty()) {
            url.push_str("&streams=");
            url.push_str(&urlencoding::encode(stream));
        }

        let resp = self.send_get(&url).await?;
        if !resp.status().is_success() {
            return Err(format!("graylog search failed: HTTP {}", resp.status()));
        }

        let body: Value = resp
            .json()
            .await
            .map_err(|e| format!("invalid graylog JSON: {}", e))?;

        Ok(parse_search_response(&body))
    }
}

fn level_to_string(value: &Value) -> String {
    match value {
        Value::String(s) => s.to_string(),
        Value::Number(n) => {
            let level = n.as_i64().unwrap_or(6);
            match level {
                i64::MIN..=2 => "critical".to_string(),
                3 => "error".to_string(),
                4 => "warning".to_string(),
                _ => "info".to_string(),
            }
        }
        other => other.to_string(),
    }
}

pub fn parse_search_response(body: &Value) -> Vec<GraylogEvent> {
    let rows = body
        .get("messages")
        .and_then(Value::as_array)
        .cloned()
        .unwrap_or_default();

    rows.into_iter()
        .filter_map(|row| {
            let message = row.get("message")?;

            let timestamp = message
                .get("timestamp")
                .and_then(Value::as_str)
                .unwrap_or("")
                .to_string();
            let source = message
                .get("source")
                .and_then(Value::as_str)
                .unwrap_or("")
                .to_string();
            let level = message
                .get("level")
                .map(level_to_string)
                .unwrap_or_else(|| "".to_string());
            let msg = message
                .get("message")
                .and_then(Value::as_str)
                .unwrap_or("")
                .to_string();
            let service = message
                .get("service")
                .and_then(Value::as_str)
                .map(str::to_string);

            if timestamp.is_empty() || source.is_empty() || msg.is_empty() {
                return None;
            }

            Some(GraylogEvent {
                timestamp,
                source,
                service,
                level,
                message: msg,
                raw: message.clone(),
            })
        })
        .collect()
}

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

    #[test]
    fn test_parse_search_response_extracts_events() {
        let payload = serde_json::json!({
            "messages": [
                { "message": {
                    "timestamp": "2026-04-17T10:00:00.000Z",
                    "source": "api-1",
                    "level": "error",
                    "message": "timeout id=42",
                    "service": "api"
                } }
            ]
        });

        let events = parse_search_response(&payload);
        assert_eq!(events.len(), 1);
        assert_eq!(events[0].source, "api-1");
        assert_eq!(events[0].service.as_deref(), Some("api"));
    }

    #[test]
    fn test_parse_search_response_maps_numeric_level() {
        let payload = serde_json::json!({
            "messages": [
                { "message": {
                    "timestamp": "2026-04-17T10:00:00.000Z",
                    "source": "worker-1",
                    "level": 4,
                    "message": "queue lag"
                } }
            ]
        });

        let events = parse_search_response(&payload);
        assert_eq!(events.len(), 1);
        assert_eq!(events[0].level, "warning");
    }

    #[test]
    fn test_parse_search_response_skips_incomplete_rows() {
        let payload = serde_json::json!({
            "messages": [
                { "message": {
                    "timestamp": "2026-04-17T10:00:00.000Z",
                    "source": "api-1"
                } }
            ]
        });

        let events = parse_search_response(&payload);
        assert!(events.is_empty());
    }
}
