use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::path::Path;

#[derive(Clone)]
pub struct MiniCloudApi {
    client: Client,
    pub server_url: String,
    pub access_token: String,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct LoginResponse {
    pub access_token: String,
    pub user: UserInfo,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct UserInfo {
    pub id: i64,
    pub username: String,
    pub role: String,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct FileEntry {
    pub id: i64,
    pub name: String,
    pub is_folder: bool,
    pub size: Option<i64>,
    pub checksum: Option<String>,
    pub updated_at: Option<String>,
    pub children: Option<Vec<FileEntry>>,
    pub locked: Option<bool>,
    pub locked_by: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct SyncTreeResponse {
    pub tree: Vec<FileEntry>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct SyncChangesResponse {
    pub changes: Vec<FileEntry>,
    pub server_time: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct LockResponse {
    pub locked: Option<bool>,
    pub locked_by: Option<String>,
    pub error: Option<String>,
}

impl MiniCloudApi {
    pub fn new(server_url: &str) -> Self {
        Self {
            client: Client::builder()
                .danger_accept_invalid_certs(false)
                .build()
                .unwrap(),
            server_url: server_url.trim_end_matches('/').to_string(),
            access_token: String::new(),
        }
    }

    fn auth_header(&self) -> String {
        format!("Bearer {}", self.access_token)
    }

    pub async fn login(&mut self, username: &str, password: &str) -> Result<LoginResponse, String> {
        let url = format!("{}/api/auth/login", self.server_url);
        let body = serde_json::json!({
            "username": username,
            "password": password,
        });

        let resp = self.client.post(&url)
            .json(&body)
            .send()
            .await
            .map_err(|e| format!("Verbindungsfehler: {}", e))?;

        if !resp.status().is_success() {
            let text = resp.text().await.unwrap_or_default();
            return Err(format!("Login fehlgeschlagen: {}", text));
        }

        let data: LoginResponse = resp.json().await
            .map_err(|e| format!("Antwort-Fehler: {}", e))?;
        self.access_token = data.access_token.clone();
        Ok(data)
    }

    pub async fn refresh_token(&mut self) -> Result<String, String> {
        let url = format!("{}/api/auth/refresh", self.server_url);
        let resp = self.client.post(&url)
            .header("Authorization", self.auth_header())
            .send()
            .await
            .map_err(|e| format!("Refresh fehlgeschlagen: {}", e))?;

        if !resp.status().is_success() {
            return Err("Token-Refresh fehlgeschlagen".to_string());
        }

        let data: serde_json::Value = resp.json().await.map_err(|e| e.to_string())?;
        if let Some(token) = data.get("access_token").and_then(|t| t.as_str()) {
            self.access_token = token.to_string();
            Ok(token.to_string())
        } else {
            Err("Kein Token in Antwort".to_string())
        }
    }

    pub async fn get_sync_tree(&self) -> Result<Vec<FileEntry>, String> {
        let url = format!("{}/api/sync/tree", self.server_url);
        let resp = self.client.get(&url)
            .header("Authorization", self.auth_header())
            .send()
            .await
            .map_err(|e| format!("Sync-Tree Fehler: {}", e))?;

        let data: SyncTreeResponse = resp.json().await
            .map_err(|e| format!("Parse-Fehler: {}", e))?;
        Ok(data.tree)
    }

    pub async fn get_changes(&self, since: &str) -> Result<SyncChangesResponse, String> {
        let url = format!("{}/api/sync/changes?since={}", self.server_url, since);
        let resp = self.client.get(&url)
            .header("Authorization", self.auth_header())
            .send()
            .await
            .map_err(|e| format!("Changes Fehler: {}", e))?;

        resp.json().await.map_err(|e| format!("Parse-Fehler: {}", e))
    }

    pub async fn download_file(&self, file_id: i64, dest: &Path) -> Result<(), String> {
        let url = format!("{}/api/files/{}/download?token={}",
            self.server_url, file_id, self.access_token);
        let resp = self.client.get(&url)
            .send()
            .await
            .map_err(|e| format!("Download Fehler: {}", e))?;

        if !resp.status().is_success() {
            return Err(format!("Download fehlgeschlagen: {}", resp.status()));
        }

        let bytes = resp.bytes().await.map_err(|e| e.to_string())?;
        if let Some(parent) = dest.parent() {
            std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
        }
        std::fs::write(dest, &bytes).map_err(|e| format!("Schreiben fehlgeschlagen: {}", e))
    }

    pub async fn upload_file(&self, file_path: &Path, parent_id: Option<i64>) -> Result<FileEntry, String> {
        let url = format!("{}/api/files/upload", self.server_url);
        let file_name = file_path.file_name()
            .and_then(|n| n.to_str())
            .unwrap_or("file")
            .to_string();

        let file_bytes = std::fs::read(file_path)
            .map_err(|e| format!("Datei lesen fehlgeschlagen: {}", e))?;

        let mut form = reqwest::multipart::Form::new()
            .part("file", reqwest::multipart::Part::bytes(file_bytes).file_name(file_name));

        if let Some(pid) = parent_id {
            form = form.text("parent_id", pid.to_string());
        }

        let resp = self.client.post(&url)
            .header("Authorization", self.auth_header())
            .multipart(form)
            .send()
            .await
            .map_err(|e| format!("Upload Fehler: {}", e))?;

        if !resp.status().is_success() {
            let text = resp.text().await.unwrap_or_default();
            return Err(format!("Upload fehlgeschlagen: {}", text));
        }

        resp.json().await.map_err(|e| format!("Parse-Fehler: {}", e))
    }

    pub async fn create_folder(&self, name: &str, parent_id: Option<i64>) -> Result<FileEntry, String> {
        let url = format!("{}/api/files/folder", self.server_url);
        let body = serde_json::json!({
            "name": name,
            "parent_id": parent_id,
        });

        let resp = self.client.post(&url)
            .header("Authorization", self.auth_header())
            .json(&body)
            .send()
            .await
            .map_err(|e| e.to_string())?;

        resp.json().await.map_err(|e| e.to_string())
    }

    pub async fn lock_file(&self, file_id: i64, client_info: &str) -> Result<(), String> {
        let url = format!("{}/api/files/{}/lock", self.server_url, file_id);
        let body = serde_json::json!({ "client_info": client_info });

        let resp = self.client.post(&url)
            .header("Authorization", self.auth_header())
            .json(&body)
            .send()
            .await
            .map_err(|e| e.to_string())?;

        if resp.status().as_u16() == 423 {
            let data: serde_json::Value = resp.json().await.map_err(|e| e.to_string())?;
            let by = data.get("locked_by").and_then(|v| v.as_str()).unwrap_or("?");
            return Err(format!("Datei gesperrt von {}", by));
        }
        if !resp.status().is_success() {
            return Err("Lock fehlgeschlagen".to_string());
        }
        Ok(())
    }

    pub async fn unlock_file(&self, file_id: i64) -> Result<(), String> {
        let url = format!("{}/api/files/{}/unlock", self.server_url, file_id);
        self.client.post(&url)
            .header("Authorization", self.auth_header())
            .send()
            .await
            .map_err(|e| e.to_string())?;
        Ok(())
    }

    pub async fn heartbeat(&self, file_id: i64) -> Result<(), String> {
        let url = format!("{}/api/files/{}/heartbeat", self.server_url, file_id);
        self.client.post(&url)
            .header("Authorization", self.auth_header())
            .send()
            .await
            .map_err(|e| e.to_string())?;
        Ok(())
    }
}
