/*
 * Hotdata API
 *
 * Powerful data platform API for datasets, queries, and analytics.
 *
 * The version of the OpenAPI document: 1.0.0
 * Contact: developers@hotdata.dev
 * Generated by: https://openapi-generator.tech
 */

use super::{configuration, ContentType, Error};
use crate::{apis::ResponseContent, models};
use reqwest;
use serde::{de::Error as _, Deserialize, Serialize};

/// struct for typed errors of method [`add_database_schema`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum AddDatabaseSchemaError {
    Status400(models::ApiErrorResponse),
    Status404(models::ApiErrorResponse),
    Status409(models::ApiErrorResponse),
    UnknownValue(serde_json::Value),
}

/// struct for typed errors of method [`add_database_table`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum AddDatabaseTableError {
    Status400(models::ApiErrorResponse),
    Status404(models::ApiErrorResponse),
    Status409(models::ApiErrorResponse),
    UnknownValue(serde_json::Value),
}

/// struct for typed errors of method [`attach_database_catalog`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum AttachDatabaseCatalogError {
    Status400(models::ApiErrorResponse),
    Status404(models::ApiErrorResponse),
    Status409(models::ApiErrorResponse),
    UnknownValue(serde_json::Value),
}

/// struct for typed errors of method [`create_database`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum CreateDatabaseError {
    Status400(models::ApiErrorResponse),
    Status500(models::ApiErrorResponse),
    UnknownValue(serde_json::Value),
}

/// struct for typed errors of method [`delete_database`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum DeleteDatabaseError {
    Status404(models::ApiErrorResponse),
    UnknownValue(serde_json::Value),
}

/// struct for typed errors of method [`detach_database_catalog`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum DetachDatabaseCatalogError {
    Status400(models::ApiErrorResponse),
    Status404(models::ApiErrorResponse),
    UnknownValue(serde_json::Value),
}

/// struct for typed errors of method [`get_database`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum GetDatabaseError {
    Status404(models::ApiErrorResponse),
    UnknownValue(serde_json::Value),
}

/// struct for typed errors of method [`list_databases`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ListDatabasesError {
    UnknownValue(serde_json::Value),
}

/// Declare a new schema (and optionally its tables) on the database's auto-created default catalog after creation. The schema becomes reachable inside the database scope (e.g. `default.<schema>.<table>` and `information_schema.schemata`) without the caller addressing the internal default connection directly. Identifiers are normalised to lowercase.
pub async fn add_database_schema(
    configuration: &configuration::Configuration,
    database_id: &str,
    add_managed_schema_request: models::AddManagedSchemaRequest,
) -> Result<models::ManagedSchemaResponse, Error<AddDatabaseSchemaError>> {
    // add a prefix to parameters to efficiently prevent name collisions
    let p_path_database_id = database_id;
    let p_body_add_managed_schema_request = add_managed_schema_request;

    let uri_str = format!(
        "{}/v1/databases/{database_id}/schemas",
        configuration.base_path,
        database_id = crate::apis::urlencode(p_path_database_id)
    );
    let mut req_builder = configuration
        .client
        .request(reqwest::Method::POST, &uri_str);

    if let Some(ref user_agent) = configuration.user_agent {
        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
    }
    if let Some(apikey) = configuration.api_keys.get("X-Workspace-Id") {
        let key = apikey.key.clone();
        let value = match apikey.prefix {
            Some(ref prefix) => format!("{} {}", prefix, key),
            None => key,
        };
        req_builder = req_builder.header("X-Workspace-Id", value);
    };
    if let Some(token) = configuration.resolve_bearer_token().await {
        req_builder = req_builder.bearer_auth(token);
    };
    req_builder = req_builder.json(&p_body_add_managed_schema_request);

    let req = req_builder.build()?;
    crate::http_log::log_request(&req);
    let resp = configuration.client.execute(req).await?;

    let status = resp.status();
    crate::http_log::log_response_status(status);
    let content_type = resp
        .headers()
        .get("content-type")
        .and_then(|v| v.to_str().ok())
        .unwrap_or("application/octet-stream");
    let content_type = super::ContentType::from(content_type);

    if !status.is_client_error() && !status.is_server_error() {
        let content = resp.text().await?;
        crate::http_log::log_response_body(&content);
        match content_type {
            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::ManagedSchemaResponse`"))),
            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::ManagedSchemaResponse`")))),
        }
    } else {
        let content = resp.text().await?;
        crate::http_log::log_response_body(&content);
        let entity: Option<AddDatabaseSchemaError> = serde_json::from_str(&content).ok();
        Err(Error::ResponseError(ResponseContent {
            status,
            content,
            entity,
        }))
    }
}

/// Declare a new table on an existing schema of the database's default catalog after creation. The table is added empty (declared-but-unloaded) and can be populated via the managed-table load endpoint targeting the default connection. Identifiers are normalised to lowercase.
pub async fn add_database_table(
    configuration: &configuration::Configuration,
    database_id: &str,
    schema: &str,
    add_managed_table_request: models::AddManagedTableRequest,
) -> Result<models::ManagedTableResponse, Error<AddDatabaseTableError>> {
    // add a prefix to parameters to efficiently prevent name collisions
    let p_path_database_id = database_id;
    let p_path_schema = schema;
    let p_body_add_managed_table_request = add_managed_table_request;

    let uri_str = format!(
        "{}/v1/databases/{database_id}/schemas/{schema}/tables",
        configuration.base_path,
        database_id = crate::apis::urlencode(p_path_database_id),
        schema = crate::apis::urlencode(p_path_schema)
    );
    let mut req_builder = configuration
        .client
        .request(reqwest::Method::POST, &uri_str);

    if let Some(ref user_agent) = configuration.user_agent {
        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
    }
    if let Some(apikey) = configuration.api_keys.get("X-Workspace-Id") {
        let key = apikey.key.clone();
        let value = match apikey.prefix {
            Some(ref prefix) => format!("{} {}", prefix, key),
            None => key,
        };
        req_builder = req_builder.header("X-Workspace-Id", value);
    };
    if let Some(token) = configuration.resolve_bearer_token().await {
        req_builder = req_builder.bearer_auth(token);
    };
    req_builder = req_builder.json(&p_body_add_managed_table_request);

    let req = req_builder.build()?;
    crate::http_log::log_request(&req);
    let resp = configuration.client.execute(req).await?;

    let status = resp.status();
    crate::http_log::log_response_status(status);
    let content_type = resp
        .headers()
        .get("content-type")
        .and_then(|v| v.to_str().ok())
        .unwrap_or("application/octet-stream");
    let content_type = super::ContentType::from(content_type);

    if !status.is_client_error() && !status.is_server_error() {
        let content = resp.text().await?;
        crate::http_log::log_response_body(&content);
        match content_type {
            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::ManagedTableResponse`"))),
            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::ManagedTableResponse`")))),
        }
    } else {
        let content = resp.text().await?;
        crate::http_log::log_response_body(&content);
        let entity: Option<AddDatabaseTableError> = serde_json::from_str(&content).ok();
        Err(Error::ResponseError(ResponseContent {
            status,
            content,
            entity,
        }))
    }
}

/// Attach an existing connection (catalog) to a database with an optional alias. Inside the database the catalog is reachable as the alias (when set) or its original name.
pub async fn attach_database_catalog(
    configuration: &configuration::Configuration,
    database_id: &str,
    attach_database_catalog_request: models::AttachDatabaseCatalogRequest,
) -> Result<(), Error<AttachDatabaseCatalogError>> {
    // add a prefix to parameters to efficiently prevent name collisions
    let p_path_database_id = database_id;
    let p_body_attach_database_catalog_request = attach_database_catalog_request;

    let uri_str = format!(
        "{}/v1/databases/{database_id}/catalogs",
        configuration.base_path,
        database_id = crate::apis::urlencode(p_path_database_id)
    );
    let mut req_builder = configuration
        .client
        .request(reqwest::Method::POST, &uri_str);

    if let Some(ref user_agent) = configuration.user_agent {
        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
    }
    if let Some(apikey) = configuration.api_keys.get("X-Workspace-Id") {
        let key = apikey.key.clone();
        let value = match apikey.prefix {
            Some(ref prefix) => format!("{} {}", prefix, key),
            None => key,
        };
        req_builder = req_builder.header("X-Workspace-Id", value);
    };
    if let Some(token) = configuration.resolve_bearer_token().await {
        req_builder = req_builder.bearer_auth(token);
    };
    req_builder = req_builder.json(&p_body_attach_database_catalog_request);

    let req = req_builder.build()?;
    crate::http_log::log_request(&req);
    let resp = configuration.client.execute(req).await?;

    let status = resp.status();
    crate::http_log::log_response_status(status);

    if !status.is_client_error() && !status.is_server_error() {
        Ok(())
    } else {
        let content = resp.text().await?;
        crate::http_log::log_response_body(&content);
        let entity: Option<AttachDatabaseCatalogError> = serde_json::from_str(&content).ok();
        Err(Error::ResponseError(ResponseContent {
            status,
            content,
            entity,
        }))
    }
}

/// Create a new database (a metadata-only grouping). A managed default catalog is auto-created and addressable inside the database as `default` (or the optional `default_catalog` name), with a `main` schema pre-declared so `default.main.<table>` works out of the box. The optional `name` is a free-form display label and is not required to be unique. Optional `default_catalog` overrides the name the default catalog answers to; it must be a valid SQL identifier and may not collide with the `hotdata`, `datasets`, or `information_schema` system catalogs. Optional `schemas` declares additional schemas/tables on the default catalog at create time; declared tables can be loaded via the standard managed-tables-load endpoint targeting `default_connection_id`. Optional `expires_at` sets when the database expires — accepts either an RFC 3339 timestamp or a relative duration suffixed with `h` (hours), `m` (minutes), or `d` (days), e.g. `24h`, `48h`, `90m`, `7d`. When omitted, the database never expires. Expiry is best-effort: the database will not be deleted before `expires_at`, but cleanup may run later than the exact timestamp. Optional `storage_backend` selects the physical backend for the default catalog — `parquet` (default) or `ducklake` (requires `ducklake.metadata_pg_url` to be configured).
pub async fn create_database(
    configuration: &configuration::Configuration,
    create_database_request: models::CreateDatabaseRequest,
) -> Result<models::CreateDatabaseResponse, Error<CreateDatabaseError>> {
    // add a prefix to parameters to efficiently prevent name collisions
    let p_body_create_database_request = create_database_request;

    let uri_str = format!("{}/v1/databases", configuration.base_path);
    let mut req_builder = configuration
        .client
        .request(reqwest::Method::POST, &uri_str);

    if let Some(ref user_agent) = configuration.user_agent {
        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
    }
    if let Some(apikey) = configuration.api_keys.get("X-Workspace-Id") {
        let key = apikey.key.clone();
        let value = match apikey.prefix {
            Some(ref prefix) => format!("{} {}", prefix, key),
            None => key,
        };
        req_builder = req_builder.header("X-Workspace-Id", value);
    };
    if let Some(token) = configuration.resolve_bearer_token().await {
        req_builder = req_builder.bearer_auth(token);
    };
    req_builder = req_builder.json(&p_body_create_database_request);

    let req = req_builder.build()?;
    crate::http_log::log_request(&req);
    let resp = configuration.client.execute(req).await?;

    let status = resp.status();
    crate::http_log::log_response_status(status);
    let content_type = resp
        .headers()
        .get("content-type")
        .and_then(|v| v.to_str().ok())
        .unwrap_or("application/octet-stream");
    let content_type = super::ContentType::from(content_type);

    if !status.is_client_error() && !status.is_server_error() {
        let content = resp.text().await?;
        crate::http_log::log_response_body(&content);
        match content_type {
            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::CreateDatabaseResponse`"))),
            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::CreateDatabaseResponse`")))),
        }
    } else {
        let content = resp.text().await?;
        crate::http_log::log_response_body(&content);
        let entity: Option<CreateDatabaseError> = serde_json::from_str(&content).ok();
        Err(Error::ResponseError(ResponseContent {
            status,
            content,
            entity,
        }))
    }
}

/// Delete a database and its auto-created default catalog. Attached catalogs are detached (their underlying connections are not deleted).
pub async fn delete_database(
    configuration: &configuration::Configuration,
    database_id: &str,
) -> Result<(), Error<DeleteDatabaseError>> {
    // add a prefix to parameters to efficiently prevent name collisions
    let p_path_database_id = database_id;

    let uri_str = format!(
        "{}/v1/databases/{database_id}",
        configuration.base_path,
        database_id = crate::apis::urlencode(p_path_database_id)
    );
    let mut req_builder = configuration
        .client
        .request(reqwest::Method::DELETE, &uri_str);

    if let Some(ref user_agent) = configuration.user_agent {
        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
    }
    if let Some(apikey) = configuration.api_keys.get("X-Workspace-Id") {
        let key = apikey.key.clone();
        let value = match apikey.prefix {
            Some(ref prefix) => format!("{} {}", prefix, key),
            None => key,
        };
        req_builder = req_builder.header("X-Workspace-Id", value);
    };
    if let Some(token) = configuration.resolve_bearer_token().await {
        req_builder = req_builder.bearer_auth(token);
    };

    let req = req_builder.build()?;
    crate::http_log::log_request(&req);
    let resp = configuration.client.execute(req).await?;

    let status = resp.status();
    crate::http_log::log_response_status(status);

    if !status.is_client_error() && !status.is_server_error() {
        Ok(())
    } else {
        let content = resp.text().await?;
        crate::http_log::log_response_body(&content);
        let entity: Option<DeleteDatabaseError> = serde_json::from_str(&content).ok();
        Err(Error::ResponseError(ResponseContent {
            status,
            content,
            entity,
        }))
    }
}

pub async fn detach_database_catalog(
    configuration: &configuration::Configuration,
    database_id: &str,
    connection_id: &str,
) -> Result<(), Error<DetachDatabaseCatalogError>> {
    // add a prefix to parameters to efficiently prevent name collisions
    let p_path_database_id = database_id;
    let p_path_connection_id = connection_id;

    let uri_str = format!(
        "{}/v1/databases/{database_id}/catalogs/{connection_id}",
        configuration.base_path,
        database_id = crate::apis::urlencode(p_path_database_id),
        connection_id = crate::apis::urlencode(p_path_connection_id)
    );
    let mut req_builder = configuration
        .client
        .request(reqwest::Method::DELETE, &uri_str);

    if let Some(ref user_agent) = configuration.user_agent {
        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
    }
    if let Some(apikey) = configuration.api_keys.get("X-Workspace-Id") {
        let key = apikey.key.clone();
        let value = match apikey.prefix {
            Some(ref prefix) => format!("{} {}", prefix, key),
            None => key,
        };
        req_builder = req_builder.header("X-Workspace-Id", value);
    };
    if let Some(token) = configuration.resolve_bearer_token().await {
        req_builder = req_builder.bearer_auth(token);
    };

    let req = req_builder.build()?;
    crate::http_log::log_request(&req);
    let resp = configuration.client.execute(req).await?;

    let status = resp.status();
    crate::http_log::log_response_status(status);

    if !status.is_client_error() && !status.is_server_error() {
        Ok(())
    } else {
        let content = resp.text().await?;
        crate::http_log::log_response_body(&content);
        let entity: Option<DetachDatabaseCatalogError> = serde_json::from_str(&content).ok();
        Err(Error::ResponseError(ResponseContent {
            status,
            content,
            entity,
        }))
    }
}

/// Fetch a database by id. The `name` field is a display label only; it is not accepted as an identifier here.
pub async fn get_database(
    configuration: &configuration::Configuration,
    database_id: &str,
) -> Result<models::DatabaseDetailResponse, Error<GetDatabaseError>> {
    // add a prefix to parameters to efficiently prevent name collisions
    let p_path_database_id = database_id;

    let uri_str = format!(
        "{}/v1/databases/{database_id}",
        configuration.base_path,
        database_id = crate::apis::urlencode(p_path_database_id)
    );
    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);

    if let Some(ref user_agent) = configuration.user_agent {
        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
    }
    if let Some(apikey) = configuration.api_keys.get("X-Workspace-Id") {
        let key = apikey.key.clone();
        let value = match apikey.prefix {
            Some(ref prefix) => format!("{} {}", prefix, key),
            None => key,
        };
        req_builder = req_builder.header("X-Workspace-Id", value);
    };
    if let Some(token) = configuration.resolve_bearer_token().await {
        req_builder = req_builder.bearer_auth(token);
    };

    let req = req_builder.build()?;
    crate::http_log::log_request(&req);
    let resp = configuration.client.execute(req).await?;

    let status = resp.status();
    crate::http_log::log_response_status(status);
    let content_type = resp
        .headers()
        .get("content-type")
        .and_then(|v| v.to_str().ok())
        .unwrap_or("application/octet-stream");
    let content_type = super::ContentType::from(content_type);

    if !status.is_client_error() && !status.is_server_error() {
        let content = resp.text().await?;
        crate::http_log::log_response_body(&content);
        match content_type {
            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::DatabaseDetailResponse`"))),
            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::DatabaseDetailResponse`")))),
        }
    } else {
        let content = resp.text().await?;
        crate::http_log::log_response_body(&content);
        let entity: Option<GetDatabaseError> = serde_json::from_str(&content).ok();
        Err(Error::ResponseError(ResponseContent {
            status,
            content,
            entity,
        }))
    }
}

pub async fn list_databases(
    configuration: &configuration::Configuration,
) -> Result<models::ListDatabasesResponse, Error<ListDatabasesError>> {
    let uri_str = format!("{}/v1/databases", configuration.base_path);
    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);

    if let Some(ref user_agent) = configuration.user_agent {
        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
    }
    if let Some(apikey) = configuration.api_keys.get("X-Workspace-Id") {
        let key = apikey.key.clone();
        let value = match apikey.prefix {
            Some(ref prefix) => format!("{} {}", prefix, key),
            None => key,
        };
        req_builder = req_builder.header("X-Workspace-Id", value);
    };
    if let Some(token) = configuration.resolve_bearer_token().await {
        req_builder = req_builder.bearer_auth(token);
    };

    let req = req_builder.build()?;
    crate::http_log::log_request(&req);
    let resp = configuration.client.execute(req).await?;

    let status = resp.status();
    crate::http_log::log_response_status(status);
    let content_type = resp
        .headers()
        .get("content-type")
        .and_then(|v| v.to_str().ok())
        .unwrap_or("application/octet-stream");
    let content_type = super::ContentType::from(content_type);

    if !status.is_client_error() && !status.is_server_error() {
        let content = resp.text().await?;
        crate::http_log::log_response_body(&content);
        match content_type {
            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::ListDatabasesResponse`"))),
            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::ListDatabasesResponse`")))),
        }
    } else {
        let content = resp.text().await?;
        crate::http_log::log_response_body(&content);
        let entity: Option<ListDatabasesError> = serde_json::from_str(&content).ok();
        Err(Error::ResponseError(ResponseContent {
            status,
            content,
            entity,
        }))
    }
}
