//! Synor Storage SDK Client.

use std::time::Duration;

use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use reqwest::{header, multipart, Client};
use serde::{de::DeserializeOwned, Deserialize, Serialize};

use super::error::{Result, StorageError};
use super::types::*;

/// Synor Storage client.
#[derive(Debug, Clone)]
pub struct SynorStorage {
    config: StorageConfig,
    client: Client,
}

#[derive(Debug, Deserialize)]
struct ApiError {
    message: String,
    code: Option<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct FileData {
    name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    content: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    cid: Option<String>,
}

#[derive(Debug, Deserialize)]
struct DirectoryResponse {
    entries: Vec<DirectoryEntry>,
}

impl SynorStorage {
    /// Create a new Storage client with the given API key.
    pub fn new(api_key: impl Into<String>) -> Self {
        Self::with_config(StorageConfig::new(api_key))
    }

    /// Create a new Storage client with custom configuration.
    pub fn with_config(config: StorageConfig) -> Self {
        let client = Client::builder()
            .timeout(Duration::from_secs(config.timeout_secs))
            .build()
            .expect("Failed to create HTTP client");

        Self { config, client }
    }

    /// Upload content to storage.
    pub async fn upload(&self, data: &[u8], options: Option<UploadOptions>) -> Result<UploadResponse> {
        let opts = options.unwrap_or_default();

        let part = multipart::Part::bytes(data.to_vec()).file_name("file");
        let form = multipart::Form::new().part("file", part);

        let mut url = format!("{}/upload", self.config.endpoint);
        let mut params = Vec::new();

        if opts.pin {
            params.push("pin=true".to_string());
        }
        if opts.wrap_with_directory {
            params.push("wrapWithDirectory=true".to_string());
        }
        if opts.cid_version != 0 {
            params.push(format!("cidVersion={}", opts.cid_version));
        }
        let algo = match opts.hash_algorithm {
            HashAlgorithm::Sha256 => "sha2-256",
            HashAlgorithm::Blake3 => "blake3",
        };
        params.push(format!("hashAlgorithm={}", algo));

        if !params.is_empty() {
            url = format!("{}?{}", url, params.join("&"));
        }

        if self.config.debug {
            println!("[SynorStorage] POST /upload");
        }

        let response = self
            .client
            .post(&url)
            .header(header::AUTHORIZATION, format!("Bearer {}", self.config.api_key))
            .multipart(form)
            .send()
            .await?;

        let status = response.status();
        if status.is_client_error() || status.is_server_error() {
            let error: ApiError = response.json().await.unwrap_or(ApiError {
                message: "Unknown error".to_string(),
                code: None,
            });
            return Err(StorageError::Api {
                message: error.message,
                status_code: status.as_u16(),
                code: error.code,
            });
        }

        let result: UploadResponse = response.json().await?;
        Ok(result)
    }

    /// Download content by CID.
    pub async fn download(&self, cid: &str, options: Option<DownloadOptions>) -> Result<Vec<u8>> {
        let opts = options.unwrap_or_default();

        let mut url = format!("{}/content/{}", self.config.endpoint, cid);
        let mut params = Vec::new();

        if let Some(offset) = opts.offset {
            params.push(format!("offset={}", offset));
        }
        if let Some(length) = opts.length {
            params.push(format!("length={}", length));
        }

        if !params.is_empty() {
            url = format!("{}?{}", url, params.join("&"));
        }

        if self.config.debug {
            println!("[SynorStorage] GET /content/{}", cid);
        }

        let response = self
            .client
            .get(&url)
            .header(header::AUTHORIZATION, format!("Bearer {}", self.config.api_key))
            .send()
            .await?;

        let status = response.status();
        if status.is_client_error() || status.is_server_error() {
            let error: ApiError = response.json().await.unwrap_or(ApiError {
                message: "Unknown error".to_string(),
                code: None,
            });
            return Err(StorageError::Api {
                message: error.message,
                status_code: status.as_u16(),
                code: error.code,
            });
        }

        let bytes = response.bytes().await?;
        Ok(bytes.to_vec())
    }

    /// Pin content by CID.
    pub async fn pin(&self, request: PinRequest) -> Result<Pin> {
        self.request("POST", "/pins", Some(&request)).await
    }

    /// Unpin content by CID.
    pub async fn unpin(&self, cid: &str) -> Result<()> {
        let path = format!("/pins/{}", cid);
        self.request::<(), _>("DELETE", &path, Option::<&()>::None)
            .await?;
        Ok(())
    }

    /// Get pin status by CID.
    pub async fn get_pin_status(&self, cid: &str) -> Result<Pin> {
        let path = format!("/pins/{}", cid);
        self.request("GET", &path, Option::<&()>::None).await
    }

    /// List pins.
    pub async fn list_pins(&self, options: Option<ListPinsOptions>) -> Result<ListPinsResponse> {
        let mut path = "/pins".to_string();
        let mut params = Vec::new();

        if let Some(opts) = options {
            if !opts.status.is_empty() {
                let statuses: Vec<&str> = opts
                    .status
                    .iter()
                    .map(|s| match s {
                        PinStatus::Queued => "queued",
                        PinStatus::Pinning => "pinning",
                        PinStatus::Pinned => "pinned",
                        PinStatus::Failed => "failed",
                        PinStatus::Unpinned => "unpinned",
                    })
                    .collect();
                params.push(format!("status={}", statuses.join(",")));
            }
            if let Some(match_type) = opts.match_type {
                let m = match match_type {
                    MatchType::Exact => "exact",
                    MatchType::IExact => "iexact",
                    MatchType::Partial => "partial",
                    MatchType::IPartial => "ipartial",
                };
                params.push(format!("match={}", m));
            }
            if let Some(name) = opts.name {
                params.push(format!("name={}", name));
            }
            if let Some(limit) = opts.limit {
                params.push(format!("limit={}", limit));
            }
            if let Some(offset) = opts.offset {
                params.push(format!("offset={}", offset));
            }
        }

        if !params.is_empty() {
            path = format!("{}?{}", path, params.join("&"));
        }

        self.request("GET", &path, Option::<&()>::None).await
    }

    /// Get gateway URL for content.
    pub fn get_gateway_url(&self, cid: &str, path: Option<&str>) -> GatewayUrl {
        let full_path = match path {
            Some(p) => format!("/{}/{}", cid, p),
            None => format!("/{}", cid),
        };
        GatewayUrl {
            url: format!("{}/ipfs{}", self.config.gateway, full_path),
            cid: cid.to_string(),
            path: path.map(|s| s.to_string()),
        }
    }

    /// Create a CAR file from files.
    pub async fn create_car(&self, files: Vec<FileEntry>) -> Result<CarFile> {
        let file_data: Vec<FileData> = files
            .into_iter()
            .map(|f| FileData {
                name: f.name,
                content: f.content.map(|c| BASE64.encode(c)),
                cid: f.cid,
            })
            .collect();

        #[derive(Serialize)]
        struct CreateCarRequest {
            files: Vec<FileData>,
        }

        self.request("POST", "/car/create", Some(&CreateCarRequest { files: file_data }))
            .await
    }

    /// Import a CAR file.
    pub async fn import_car(&self, car_data: &[u8], pin: bool) -> Result<ImportCarResponse> {
        #[derive(Serialize)]
        struct ImportCarRequest {
            car: String,
            pin: bool,
        }

        let request = ImportCarRequest {
            car: BASE64.encode(car_data),
            pin,
        };

        self.request("POST", "/car/import", Some(&request)).await
    }

    /// Export content as a CAR file.
    pub async fn export_car(&self, cid: &str) -> Result<Vec<u8>> {
        let url = format!("{}/car/export/{}", self.config.endpoint, cid);

        let response = self
            .client
            .get(&url)
            .header(header::AUTHORIZATION, format!("Bearer {}", self.config.api_key))
            .send()
            .await?;

        let status = response.status();
        if status.is_client_error() || status.is_server_error() {
            let error: ApiError = response.json().await.unwrap_or(ApiError {
                message: "Unknown error".to_string(),
                code: None,
            });
            return Err(StorageError::Api {
                message: error.message,
                status_code: status.as_u16(),
                code: error.code,
            });
        }

        let bytes = response.bytes().await?;
        Ok(bytes.to_vec())
    }

    /// Create a directory from files.
    pub async fn create_directory(&self, files: Vec<FileEntry>) -> Result<UploadResponse> {
        let file_data: Vec<FileData> = files
            .into_iter()
            .map(|f| FileData {
                name: f.name,
                content: f.content.map(|c| BASE64.encode(c)),
                cid: f.cid,
            })
            .collect();

        #[derive(Serialize)]
        struct CreateDirRequest {
            files: Vec<FileData>,
        }

        self.request("POST", "/directory", Some(&CreateDirRequest { files: file_data }))
            .await
    }

    /// List directory contents.
    pub async fn list_directory(&self, cid: &str, path: Option<&str>) -> Result<Vec<DirectoryEntry>> {
        let mut api_path = format!("/directory/{}", cid);
        if let Some(p) = path {
            api_path = format!("{}?path={}", api_path, urlencoding::encode(p));
        }

        let response: DirectoryResponse = self.request("GET", &api_path, Option::<&()>::None).await?;
        Ok(response.entries)
    }

    /// Get storage statistics.
    pub async fn get_stats(&self) -> Result<StorageStats> {
        self.request("GET", "/stats", Option::<&()>::None).await
    }

    /// Check if content exists.
    pub async fn exists(&self, cid: &str) -> Result<bool> {
        let url = format!("{}/content/{}", self.config.endpoint, cid);

        let response = self
            .client
            .head(&url)
            .header(header::AUTHORIZATION, format!("Bearer {}", self.config.api_key))
            .send()
            .await;

        match response {
            Ok(resp) => Ok(resp.status().is_success()),
            Err(_) => Ok(false),
        }
    }

    /// Get content metadata.
    pub async fn get_metadata(&self, cid: &str) -> Result<serde_json::Value> {
        let path = format!("/content/{}/metadata", cid);
        self.request("GET", &path, Option::<&()>::None).await
    }

    /// Internal HTTP request method.
    async fn request<T, B>(&self, method: &str, path: &str, body: Option<&B>) -> Result<T>
    where
        T: DeserializeOwned,
        B: Serialize,
    {
        let url = format!("{}{}", self.config.endpoint, path);

        if self.config.debug {
            println!("[SynorStorage] {} {}", method, path);
        }

        let mut request = match method {
            "GET" => self.client.get(&url),
            "POST" => self.client.post(&url),
            "PUT" => self.client.put(&url),
            "DELETE" => self.client.delete(&url),
            "HEAD" => self.client.head(&url),
            _ => {
                return Err(StorageError::Validation(format!(
                    "Invalid method: {}",
                    method
                )))
            }
        };

        request = request
            .header(header::AUTHORIZATION, format!("Bearer {}", self.config.api_key))
            .header(header::CONTENT_TYPE, "application/json");

        if let Some(b) = body {
            request = request.json(b);
        }

        let response = request.send().await?;
        let status = response.status();

        if status.is_client_error() || status.is_server_error() {
            let error: ApiError = response.json().await.unwrap_or(ApiError {
                message: "Unknown error".to_string(),
                code: None,
            });
            return Err(StorageError::Api {
                message: error.message,
                status_code: status.as_u16(),
                code: error.code,
            });
        }

        if status == reqwest::StatusCode::NO_CONTENT {
            // For DELETE operations that return 204
            return serde_json::from_str("{}").map_err(StorageError::from);
        }

        let result: T = response.json().await?;
        Ok(result)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_config_builder() {
        let config = StorageConfig::new("test-key")
            .endpoint("https://custom.endpoint")
            .gateway("https://custom.gateway")
            .chunk_size(512000)
            .timeout(60)
            .debug(true);

        assert_eq!(config.api_key, "test-key");
        assert_eq!(config.endpoint, "https://custom.endpoint");
        assert_eq!(config.gateway, "https://custom.gateway");
        assert_eq!(config.chunk_size, 512000);
        assert_eq!(config.timeout_secs, 60);
        assert!(config.debug);
    }

    #[test]
    fn test_client_creation() {
        let client = SynorStorage::new("test-key");
        assert_eq!(client.config.api_key, "test-key");
    }

    #[test]
    fn test_gateway_url() {
        let client = SynorStorage::new("test-key");

        let url = client.get_gateway_url("QmTest123", None);
        assert_eq!(url.url, "https://gateway.synor.cc/ipfs/QmTest123");
        assert_eq!(url.cid, "QmTest123");
        assert!(url.path.is_none());

        let url_with_path = client.get_gateway_url("QmTest123", Some("subdir/file.txt"));
        assert_eq!(
            url_with_path.url,
            "https://gateway.synor.cc/ipfs/QmTest123/subdir/file.txt"
        );
        assert_eq!(url_with_path.path, Some("subdir/file.txt".to_string()));
    }
}
