use anyhow::{Context, Result};
use tracing::{debug, instrument};

use crate::types::{
    ClusterStatusResponse, HealthResponse, RangeInfoDto, RangesWrapper, ShardInfoResponse,
};

/// HTTP client for StemeDB Gateway API
pub struct AdminClient {
    base_url: String,
    client: reqwest::Client,
}

impl AdminClient {
    /// Create a new admin client pointing to the gateway
    pub fn new(base_url: String) -> Self {
        let client = reqwest::Client::builder()
            .timeout(std::time::Duration::from_secs(30))
            .build()
            .expect("Failed to build HTTP client");

        Self { base_url, client }
    }

    /// Check gateway health
    #[instrument(skip(self))]
    pub async fn health(&self) -> Result<HealthResponse> {
        let url = format!("{}/v1/health", self.base_url);
        debug!("Fetching health from: {}", url);

        let response = self
            .client
            .get(&url)
            .send()
            .await
            .context(format!("Failed to connect to gateway at {}", self.base_url))?;

        if !response.status().is_success() {
            anyhow::bail!(
                "Gateway returned error status: {} - {}",
                response.status(),
                response.text().await.unwrap_or_default()
            );
        }

        response.json().await.context("Failed to parse health response")
    }

    /// Get cluster status overview
    #[instrument(skip(self))]
    pub async fn cluster_status(&self) -> Result<ClusterStatusResponse> {
        let url = format!("{}/v1/cluster/status", self.base_url);
        debug!("Fetching cluster status from: {}", url);

        let response = self
            .client
            .get(&url)
            .send()
            .await
            .context(format!("Failed to connect to gateway at {}", self.base_url))?;

        if !response.status().is_success() {
            anyhow::bail!(
                "Gateway returned error status: {} - {}",
                response.status(),
                response.text().await.unwrap_or_default()
            );
        }

        response.json().await.context("Failed to parse cluster status response")
    }

    /// Get detailed information about a specific shard
    #[instrument(skip(self))]
    pub async fn shard_info(&self, shard_id: u32) -> Result<RangeInfoDto> {
        let url = format!("{}/v1/shards/{}", self.base_url, shard_id);
        debug!("Fetching shard info from: {}", url);

        let response = self
            .client
            .get(&url)
            .send()
            .await
            .context(format!("Failed to connect to gateway at {}", self.base_url))?;

        if !response.status().is_success() {
            if response.status() == reqwest::StatusCode::NOT_FOUND {
                anyhow::bail!("Shard not found: {}", shard_id);
            }
            anyhow::bail!(
                "Gateway returned error status: {} - {}",
                response.status(),
                response.text().await.unwrap_or_default()
            );
        }

        // Gateway returns different format than /admin/ranges, so convert it
        let shard_response: ShardInfoResponse =
            response.json().await.context("Failed to parse shard info response")?;

        Ok(shard_response.into())
    }

    /// Get information about all shards
    #[instrument(skip(self))]
    pub async fn all_ranges(&self) -> Result<Vec<RangeInfoDto>> {
        let url = format!("{}/v1/admin/ranges", self.base_url);
        debug!("Fetching all ranges from: {}", url);

        let response = self
            .client
            .get(&url)
            .send()
            .await
            .context(format!("Failed to connect to gateway at {}", self.base_url))?;

        if !response.status().is_success() {
            anyhow::bail!(
                "Gateway returned error status: {} - {}",
                response.status(),
                response.text().await.unwrap_or_default()
            );
        }

        // Gateway returns {"ranges": [...]} so we need to unwrap it
        let wrapper: RangesWrapper =
            response.json().await.context("Failed to parse ranges response")?;

        Ok(wrapper.ranges)
    }

    /// Trigger anti-entropy sync (Phase 2 feature - not yet exposed in CLI)
    #[allow(dead_code)]
    #[instrument(skip(self))]
    pub async fn force_sync(&self) -> Result<()> {
        let url = format!("{}/v1/admin/sync", self.base_url);
        debug!("Triggering sync at: {}", url);

        let response = self
            .client
            .post(&url)
            .send()
            .await
            .context(format!("Failed to connect to gateway at {}", self.base_url))?;

        if !response.status().is_success() {
            anyhow::bail!(
                "Gateway returned error status: {} - {}",
                response.status(),
                response.text().await.unwrap_or_default()
            );
        }

        Ok(())
    }
}
