use crate::config::AppConfig;
use crate::error::{AppError, AppResult};
use serde::{Deserialize, Serialize};

const HTTP_TIMEOUT_SECONDS: u64 = 30;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MdmPushStatus {
    pub udid: String,
    pub push_result: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MdmEnqueueResult {
    pub command_uuid: String,
    pub response_body: String,
}

pub struct MdmClient {
    client: reqwest::Client,
    server: String,
    auth_header: String,
}

impl MdmClient {
    pub fn new() -> AppResult<Self> {
        let cfg = AppConfig::load_binder_config()?;
        let creds = format!("{}:{}", cfg.mdm_user, cfg.mdm_api_key);
        use base64::{engine::general_purpose::STANDARD, Engine};
        let auth_header = format!("Basic {}", STANDARD.encode(creds));

        Ok(Self {
            client: reqwest::Client::builder()
                .timeout(std::time::Duration::from_secs(HTTP_TIMEOUT_SECONDS))
                .build()
                .map_err(|e| AppError::new(format!("reqwest client build: {}", e)))?,
            server: cfg.mdm_server,
            auth_header,
        })
    }

    fn url(&self, path: &str) -> AppResult<String> {
        let base = self.server.trim_end_matches('/');
        Ok(format!("{}{}", base, path))
    }

    fn request(&self, method: reqwest::Method, path: &str) -> AppResult<reqwest::RequestBuilder> {
        let url = self.url(path)?;
        Ok(self
            .client
            .request(method, &url)
            .header("Authorization", &self.auth_header))
    }

    pub async fn ping(&self) -> AppResult<String> {
        let resp = self
            .request(reqwest::Method::GET, "/version")?
            .send()
            .await
            .map_err(|e| AppError::new(format!("MDM ping failed: {}", e)))?;

        let status = resp.status();
        let body = resp
            .text()
            .await
            .unwrap_or_else(|_| "Unknown error".to_string());

        if !status.is_success() {
            return Err(AppError::new(format!("MDM ping HTTP {}: {}", status, body)));
        }
        Ok(body)
    }

    pub async fn push(&self, udid: &str) -> AppResult<MdmPushStatus> {
        let resp = self
            .request(reqwest::Method::GET, &format!("/v1/push/{}", udid))?
            .send()
            .await
            .map_err(|e| AppError::new(format!("MDM push failed: {}", e)))?;

        let status = resp.status();
        let body = resp
            .text()
            .await
            .unwrap_or_else(|_| "Unknown error".to_string());

        if !status.is_success() {
            return Err(AppError::new(format!("MDM push HTTP {}: {}", status, body)));
        }

        // Parse NanoMDM response: { "status": { "<udid>": { "push_result": "..." } } }
        let parsed: serde_json::Value = serde_json::from_str(&body)
            .map_err(|e| AppError::new(format!("MDM push parse error: {} — body: {}", e, body)))?;

        let push_result = parsed
            .get("status")
            .and_then(|s| s.get(udid))
            .and_then(|d| d.get("push_result"))
            .and_then(|v| v.as_str())
            .ok_or_else(|| AppError::new(format!("MDM push response unerwartet: {}", body)))?
            .to_string();

        Ok(MdmPushStatus {
            udid: udid.to_string(),
            push_result,
        })
    }

    pub async fn enqueue(&self, udid: &str, command: serde_json::Value) -> AppResult<MdmEnqueueResult> {
        let command_uuid = uuid::Uuid::new_v4().to_string();
        let envelope = serde_json::json!({
            "CommandUUID": command_uuid,
            "Command": command,
        });

        // NanoMDM expects plist, but also accepts JSON in newer versions.
        // For maximum compatibility we send JSON here; if it fails, the caller
        // will see the error.
        let resp = self
            .request(reqwest::Method::PUT, &format!("/v1/enqueue/{}?push=1", udid))?
            .header("Content-Type", "application/json")
            .json(&envelope)
            .send()
            .await
            .map_err(|e| AppError::new(format!("MDM enqueue failed: {}", e)))?;

        let status = resp.status();
        let body = resp
            .text()
            .await
            .unwrap_or_else(|_| "Unknown error".to_string());

        if !status.is_success() {
            return Err(AppError::new(format!("MDM enqueue HTTP {}: {}", status, body)));
        }

        Ok(MdmEnqueueResult {
            command_uuid,
            response_body: body,
        })
    }
}
