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 RedeemPairingRequest {
    pub code: String,
    pub label: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RedeemPairingResponse {
    pub token: String,
    #[serde(rename = "sessionId")]
    pub session_id: String,
    #[serde(rename = "createdAt")]
    pub created_at: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegisterDeviceRequest {
    #[serde(rename = "deviceId", skip_serializing_if = "Option::is_none")]
    pub device_id: Option<String>,
    #[serde(rename = "hardwareId", skip_serializing_if = "Option::is_none")]
    pub hardware_id: Option<String>,
    pub hostname: String,
    pub model: Option<String>,
    #[serde(rename = "osVersion")]
    pub os_version: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegisterDeviceResponse {
    #[serde(rename = "deviceId")]
    pub device_id: String,
    #[serde(rename = "dnsToken")]
    pub dns_token: String,
    #[serde(rename = "profileUrl")]
    pub profile_url: String,
    pub existing: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MagicDeviceInfo {
    pub source: String,
    #[serde(rename = "deviceId")]
    pub device_id: String,
    #[serde(rename = "hardwareId")]
    pub hardware_id: Option<String>,
    pub hostname: String,
    pub model: Option<String>,
    #[serde(rename = "osVersion")]
    pub os_version: Option<String>,
    #[serde(default, rename = "mdmId")]
    pub mdm_id: Option<String>,
    #[serde(rename = "magicEnrolledAt")]
    pub magic_enrolled_at: Option<String>,
    #[serde(rename = "releaseRequestedAt")]
    pub release_requested_at: Option<String>,
    #[serde(rename = "releaseAvailableAt")]
    pub release_available_at: Option<String>,
    #[serde(default, rename = "cooldownUntil")]
    pub cooldown_until: Option<String>,
    #[serde(default = "default_active_status")]
    pub status: String,
    #[serde(default, rename = "lastSeenAt")]
    pub last_seen_at: Option<String>,
}

fn default_active_status() -> String {
    "active".to_string()
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StatusResponse {
    pub active: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReleaseResponse {
    #[serde(rename = "releaseRequestedAt")]
    pub release_requested_at: String,
    #[serde(rename = "releaseAvailableAt")]
    pub release_available_at: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CooldownResponse {
    #[serde(rename = "cooldownUntil")]
    pub cooldown_until: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserProfile {
    pub nickname: Option<String>,
    pub avatar: Option<String>,
    pub plan: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MdmStatusData {
    pub enrolled: bool,
    pub company: Option<String>,
    pub supervised: bool,
    #[serde(rename = "lockProfileInstalled")]
    pub lock_profile_installed: bool,
    #[serde(rename = "lastAppPushAt")]
    pub last_app_push_at: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MdmStatusByUdidData {
    pub enrolled: bool,
    pub company: Option<String>,
    pub supervised: bool,
    #[serde(rename = "lastAppPushAt")]
    pub last_app_push_at: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MdmLinkRequest {
    #[serde(rename = "mdmId")]
    pub mdm_id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReportProtectionStateRequest {
    #[serde(rename = "deviceId")]
    pub device_id: String,
    pub platform: String,
    #[serde(rename = "protectionType")]
    pub protection_type: String,
    pub active: bool,
    pub reason: Option<String>,
    pub source: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiEnvelope<T> {
    pub success: bool,
    pub data: T,
}

pub struct MagicApiClient {
    client: reqwest::Client,
    base_url: String,
}

impl MagicApiClient {
    pub fn new(config: &AppConfig) -> Self {
        Self {
            client: reqwest::Client::builder()
                .timeout(std::time::Duration::from_secs(HTTP_TIMEOUT_SECONDS))
                .build()
                .expect("reqwest client build"),
            base_url: config.backend_base_url.clone(),
        }
    }

    pub async fn redeem_pairing_code(
        &self,
        code: &str,
        label: Option<&str>,
    ) -> AppResult<RedeemPairingResponse> {
        let url = format!("{}/api/magic/pair/redeem", self.base_url);
        let body = RedeemPairingRequest {
            code: code.to_string(),
            label: label.map(|s| s.to_string()),
        };

        let response = self
            .client
            .post(&url)
            .json(&body)
            .send()
            .await
            .map_err(|e| AppError::new(format!("Network error: {}", e)))?;

        let status = response.status();
        if status == reqwest::StatusCode::BAD_REQUEST || status == reqwest::StatusCode::NOT_FOUND {
            return Err(AppError::new(
                "Code ungültig. Bitte in der ReBreak-App einen neuen Code generieren.".to_string(),
            ));
        }
        if status == reqwest::StatusCode::GONE {
            let text = response
                .text()
                .await
                .unwrap_or_else(|_| "".to_string());
            if text.contains("verwendet") {
                return Err(AppError::new("Code wurde bereits verwendet.".to_string()));
            }
            return Err(AppError::new("Code abgelaufen. Bitte einen neuen generieren.".to_string()));
        }

        Self::handle_response::<ApiEnvelope<RedeemPairingResponse>>(response)
            .await
            .map(|envelope| envelope.data)
    }

    pub async fn register_device(
        &self,
        token: &str,
        device_id: Option<&str>,
        hardware_id: Option<&str>,
        hostname: &str,
        model: Option<&str>,
        os_version: Option<&str>,
    ) -> AppResult<RegisterDeviceResponse> {
        let url = format!("{}/api/magic/register", self.base_url);
        let body = RegisterDeviceRequest {
            device_id: device_id.map(|s| s.to_string()),
            hardware_id: hardware_id.map(|s| s.to_string()),
            hostname: hostname.to_string(),
            model: model.map(|s| s.to_string()),
            os_version: os_version.map(|s| s.to_string()),
        };

        let response = self
            .client
            .post(&url)
            .header("Authorization", format!("Bearer {}", token))
            .json(&body)
            .send()
            .await
            .map_err(|e| AppError::new(format!("Network error: {}", e)))?;

        let status = response.status();
        if status == reqwest::StatusCode::CONFLICT {
            return Err(AppError::new(
                "Device-Limit erreicht. Bitte zuerst ein Gerät freigeben.".to_string(),
            ));
        }

        Self::handle_response::<ApiEnvelope<RegisterDeviceResponse>>(response)
            .await
            .map(|envelope| envelope.data)
    }

    pub async fn list_devices(&self, token: &str) -> AppResult<Vec<MagicDeviceInfo>> {
        let url = format!("{}/api/magic/devices", self.base_url);

        let response = self
            .client
            .get(&url)
            .header("Authorization", format!("Bearer {}", token))
            .send()
            .await
            .map_err(|e| AppError::new(format!("Network error: {}", e)))?;

        Self::handle_response::<ApiEnvelope<Vec<MagicDeviceInfo>>>(response)
            .await
            .map(|envelope| envelope.data)
    }

    pub async fn get_status(&self, dns_token: &str) -> AppResult<bool> {
        let url = format!("{}/api/magic/status", self.base_url);

        let response = self
            .client
            .get(&url)
            .query(&[("token", dns_token)])
            .send()
            .await
            .map_err(|e| AppError::new(format!("Network error: {}", e)))?;

        Self::handle_response::<ApiEnvelope<StatusResponse>>(response)
            .await
            .map(|envelope| envelope.data.active)
    }

    pub async fn request_release(&self, token: &str, device_id: &str) -> AppResult<ReleaseResponse> {
        let url = format!(
            "{}/api/magic/devices/{}/request-release",
            self.base_url, device_id
        );

        let response = self
            .client
            .post(&url)
            .header("Authorization", format!("Bearer {}", token))
            .send()
            .await
            .map_err(|e| AppError::new(format!("Network error: {}", e)))?;

        Self::handle_response::<ApiEnvelope<ReleaseResponse>>(response)
            .await
            .map(|envelope| envelope.data)
    }

    pub async fn cancel_release(&self, token: &str, device_id: &str) -> AppResult<()> {
        let url = format!(
            "{}/api/magic/devices/{}/cancel-release",
            self.base_url, device_id
        );

        let response = self
            .client
            .post(&url)
            .header("Authorization", format!("Bearer {}", token))
            .send()
            .await
            .map_err(|e| AppError::new(format!("Network error: {}", e)))?;

        Self::handle_response::<ApiEnvelope<serde_json::Value>>(response)
            .await
            .map(|_| ())
    }

    pub async fn start_cooldown(
        &self,
        token: &str,
        device_id: &str,
        duration_minutes: u32,
    ) -> AppResult<CooldownResponse> {
        let url = format!("{}/api/magic/devices/{}/cooldown", self.base_url, device_id);
        let response = self
            .client
            .post(&url)
            .header("Authorization", format!("Bearer {}", token))
            .json(&serde_json::json!({ "durationMinutes": duration_minutes }))
            .send()
            .await
            .map_err(|e| AppError::new(format!("Network error: {}", e)))?;
        Self::handle_response::<ApiEnvelope<CooldownResponse>>(response)
            .await
            .map(|envelope| envelope.data)
    }

    pub async fn cancel_cooldown(&self, token: &str, device_id: &str) -> AppResult<()> {
        let url = format!("{}/api/magic/devices/{}/cancel-cooldown", self.base_url, device_id);
        let response = self
            .client
            .post(&url)
            .header("Authorization", format!("Bearer {}", token))
            .send()
            .await
            .map_err(|e| AppError::new(format!("Network error: {}", e)))?;
        Self::handle_response::<ApiEnvelope<serde_json::Value>>(response)
            .await
            .map(|_| ())
    }

    pub async fn fetch_me(&self, token: &str) -> AppResult<UserProfile> {
        let url = format!("{}/api/magic/me", self.base_url);

        let response = self
            .client
            .get(&url)
            .header("Authorization", format!("Bearer {}", token))
            .send()
            .await
            .map_err(|e| AppError::new(format!("Network error: {}", e)))?;

        Self::handle_response::<ApiEnvelope<UserProfile>>(response)
            .await
            .map(|envelope| envelope.data)
    }

    pub async fn download_profile(&self, dns_token: &str) -> AppResult<Vec<u8>> {
        let url = format!(
            "{}/api/magic/profile.mobileconfig?token={}",
            self.base_url, dns_token
        );

        let response = self
            .client
            .get(&url)
            .send()
            .await
            .map_err(|e| AppError::new(format!("Network error: {}", e)))?;

        if !response.status().is_success() {
            let status = response.status();
            let text = response.text().await.unwrap_or_else(|_| "Unknown error".to_string());
            return Err(AppError::new(format!("HTTP {}: {}", status, text)));
        }

        response
            .bytes()
            .await
            .map(|b| b.to_vec())
            .map_err(|e| AppError::new(format!("Failed to read profile: {}", e)))
    }

    pub async fn get_mdm_status(&self, token: &str, device_id: &str) -> AppResult<MdmStatusData> {
        let url = format!("{}/api/magic/devices/{}/mdm", self.base_url, device_id);

        let response = self
            .client
            .get(&url)
            .header("Authorization", format!("Bearer {}", token))
            .send()
            .await
            .map_err(|e| AppError::new(format!("Network error: {}", e)))?;

        Self::handle_response::<ApiEnvelope<MdmStatusData>>(response)
            .await
            .map(|envelope| envelope.data)
    }

    pub async fn get_mdm_status_by_udid(
        &self,
        token: &str,
        udid: &str,
    ) -> AppResult<MdmStatusByUdidData> {
        let url = format!("{}/api/magic/mdm/by-udid", self.base_url);

        let response = self
            .client
            .get(&url)
            .header("Authorization", format!("Bearer {}", token))
            .query(&[("udid", udid)])
            .send()
            .await
            .map_err(|e| AppError::new(format!("Network error: {}", e)))?;

        Self::handle_response::<ApiEnvelope<MdmStatusByUdidData>>(response)
            .await
            .map(|envelope| envelope.data)
    }

    pub async fn link_mdm_device(
        &self,
        token: &str,
        device_id: &str,
        mdm_id: &str,
    ) -> AppResult<()> {
        let url = format!(
            "{}/api/magic/devices/{}/mdm-link",
            self.base_url, device_id
        );

        let response = self
            .client
            .post(&url)
            .header("Authorization", format!("Bearer {}", token))
            .json(&MdmLinkRequest {
                mdm_id: mdm_id.to_string(),
            })
            .send()
            .await
            .map_err(|e| AppError::new(format!("Network error: {}", e)))?;

        Self::handle_response::<ApiEnvelope<serde_json::Value>>(response)
            .await
            .map(|_| ())
    }

    pub async fn report_device_protection_state(
        &self,
        token: &str,
        device_id: &str,
        platform: &str,
        protection_type: &str,
        active: bool,
        reason: Option<&str>,
    ) -> AppResult<()> {
        let url = format!("{}/api/devices/protection-state", self.base_url);

        let response = self
            .client
            .post(&url)
            .header("Authorization", format!("Bearer {}", token))
            .json(&ReportProtectionStateRequest {
                device_id: device_id.to_string(),
                platform: platform.to_string(),
                protection_type: protection_type.to_string(),
                active,
                reason: reason.map(|s| s.to_string()),
                source: Some("magic".to_string()),
            })
            .send()
            .await
            .map_err(|e| AppError::new(format!("Network error: {}", e)))?;

        Self::handle_response::<ApiEnvelope<serde_json::Value>>(response)
            .await
            .map(|_| ())
    }

    async fn handle_response<T: serde::de::DeserializeOwned>(
        response: reqwest::Response,
    ) -> AppResult<T> {
        let status = response.status();
        let body = response
            .text()
            .await
            .unwrap_or_else(|_| "<failed to read body>".to_string());
        if status.is_success() {
            serde_json::from_str::<T>(&body).map_err(|e| {
                AppError::new(format!(
                    "Failed to parse response: {} | body={}",
                    e, body
                ))
            })
        } else {
            Err(AppError::new(format!("HTTP {}: {}", status, body)))
        }
    }
}
