use anyhow::Result;
use reqwest::Client;
use serde::de::DeserializeOwned;
use std::time::Duration;

use super::types::*;

#[derive(Debug, thiserror::Error)]
pub enum ApiError {
    #[error("HTTP {status}: {message}")]
    Http { status: u16, message: String },

    #[error("Cloudflare blocked the request (403). The API may be down or rate-limiting.")]
    CloudflareBlocked,

    #[error("Request failed: {0}")]
    Request(#[from] reqwest::Error),

    #[error("JSON parse error: {0}")]
    Parse(#[from] serde_json::Error),
}

pub struct LeagueApiClient {
    client: Client,
    base_url: String,
    api_key: String,
}

impl LeagueApiClient {
    pub fn new(base_url: &str, api_key: &str, timeout_secs: u64) -> Result<Self> {
        let client = Client::builder()
            .timeout(Duration::from_secs(timeout_secs))
            .build()?;

        Ok(Self {
            client,
            base_url: base_url.trim_end_matches('/').to_string(),
            api_key: api_key.to_string(),
        })
    }

    async fn get<T: DeserializeOwned>(
        &self,
        path: &str,
        params: &[(String, String)],
    ) -> Result<T, ApiError> {
        let url = format!("{}/api/v3{}", self.base_url, path);
        let mut req = self.client.get(&url).query(params);
        if !self.api_key.is_empty() {
            req = req.bearer_auth(&self.api_key);
        }
        let response = req.send().await?;
        let status = response.status();
        if !status.is_success() {
            let body = response.text().await.unwrap_or_default();
            if status.as_u16() == 403 && body.to_lowercase().contains("cloudflare") {
                return Err(ApiError::CloudflareBlocked);
            }
            return Err(ApiError::Http { status: status.as_u16(), message: body });
        }
        let body = response.text().await.map_err(ApiError::Request)?;
        let json = serde_json::from_str::<T>(&body)?;
        Ok(json)
    }

    // -------------------------------------------------------------------------
    // Public endpoint methods
    // -------------------------------------------------------------------------

    pub async fn get_teams(
        &self,
        season: Option<i64>,
        team_abbrev: Option<&[&str]>,
        active_only: bool,
        short_output: bool,
    ) -> Result<TeamsResponse, ApiError> {
        let mut params: Vec<(String, String)> = Vec::new();
        if let Some(s) = season {
            params.push(("season".to_string(), s.to_string()));
        }
        if let Some(abbrevs) = team_abbrev {
            for a in abbrevs {
                params.push(("team_abbrev".to_string(), a.to_string()));
            }
        }
        if active_only {
            params.push(("active_only".to_string(), "true".to_string()));
        }
        if short_output {
            params.push(("short_output".to_string(), "true".to_string()));
        }
        self.get("/teams", &params).await
    }

    pub async fn get_team(&self, team_id: i64) -> Result<TeamData, ApiError> {
        self.get(&format!("/teams/{}", team_id), &[]).await
    }

    pub async fn get_team_roster(
        &self,
        team_id: i64,
        which: &str,
    ) -> Result<serde_json::Value, ApiError> {
        self.get(&format!("/teams/{}/roster/{}", team_id, which), &[]).await
    }

    pub async fn get_players(
        &self,
        season: Option<i64>,
        team_id: Option<&[i64]>,
        pos: Option<&[&str]>,
        name: Option<&str>,
        short_output: bool,
    ) -> Result<PlayersResponse, ApiError> {
        let mut params: Vec<(String, String)> = Vec::new();
        if let Some(s) = season {
            params.push(("season".to_string(), s.to_string()));
        }
        if let Some(ids) = team_id {
            for id in ids {
                params.push(("team_id".to_string(), id.to_string()));
            }
        }
        if let Some(positions) = pos {
            for p in positions {
                params.push(("pos".to_string(), p.to_string()));
            }
        }
        if let Some(n) = name {
            params.push(("name".to_string(), n.to_string()));
        }
        if short_output {
            params.push(("short_output".to_string(), "true".to_string()));
        }
        self.get("/players", &params).await
    }

    pub async fn get_player(
        &self,
        player_id: i64,
        short_output: bool,
    ) -> Result<PlayerData, ApiError> {
        let mut params: Vec<(String, String)> = Vec::new();
        if short_output {
            params.push(("short_output".to_string(), "true".to_string()));
        }
        self.get(&format!("/players/{}", player_id), &params).await
    }

    pub async fn search_players(
        &self,
        query: &str,
        season: Option<i64>,
        limit: Option<i64>,
    ) -> Result<PlayersResponse, ApiError> {
        let mut params: Vec<(String, String)> = Vec::new();
        params.push(("q".to_string(), query.to_string()));
        if let Some(s) = season {
            params.push(("season".to_string(), s.to_string()));
        }
        if let Some(l) = limit {
            params.push(("limit".to_string(), l.to_string()));
        }
        self.get("/players/search", &params).await
    }

    pub async fn get_transactions(
        &self,
        season: i64,
        week_start: i64,
        week_end: Option<i64>,
        team_abbrev: Option<&[&str]>,
        cancelled: bool,
        frozen: bool,
        short_output: bool,
    ) -> Result<TransactionsResponse, ApiError> {
        let mut params: Vec<(String, String)> = Vec::new();
        params.push(("season".to_string(), season.to_string()));
        params.push(("week_start".to_string(), week_start.to_string()));
        if let Some(abbrevs) = team_abbrev {
            for a in abbrevs {
                params.push(("team_abbrev".to_string(), a.to_string()));
            }
        }
        if let Some(we) = week_end {
            params.push(("week_end".to_string(), we.to_string()));
        }
        if cancelled {
            params.push(("cancelled".to_string(), "true".to_string()));
        }
        if frozen {
            params.push(("frozen".to_string(), "true".to_string()));
        }
        if short_output {
            params.push(("short_output".to_string(), "true".to_string()));
        }
        self.get("/transactions", &params).await
    }

    pub async fn get_current(&self) -> Result<CurrentResponse, ApiError> {
        self.get("/current", &[]).await
    }

    pub async fn get_schedule(
        &self,
        season: i64,
        week: Option<i64>,
        team_id: Option<i64>,
    ) -> Result<serde_json::Value, ApiError> {
        let mut params: Vec<(String, String)> = Vec::new();
        params.push(("season".to_string(), season.to_string()));
        if let Some(w) = week {
            params.push(("week".to_string(), w.to_string()));
        }
        if let Some(t) = team_id {
            params.push(("team_id".to_string(), t.to_string()));
        }
        self.get("/schedules", &params).await
    }

    pub async fn get_standings(&self, season: i64) -> Result<serde_json::Value, ApiError> {
        let params = vec![("season".to_string(), season.to_string())];
        self.get("/standings", &params).await
    }
}
