use std::path::Path;

use aws_sdk_s3::config::{BehaviorVersion, Credentials, Region};
use aws_sdk_s3::primitives::ByteStream;
use aws_sdk_s3::types::{Delete, ObjectIdentifier};
use aws_sdk_s3::Client;
use uuid::Uuid;

use crate::config::Config;
use crate::state::AppState;

pub fn client(config: &Config) -> Client {
    let credentials = Credentials::new(
        &config.s3_access_key,
        &config.s3_secret_key,
        None,
        None,
        "photos-config",
    );
    let mut builder = aws_sdk_s3::Config::builder()
        .behavior_version(BehaviorVersion::latest())
        .credentials_provider(credentials)
        .region(Region::new(config.s3_region.clone()))
        .force_path_style(config.s3_force_path_style);
    if let Some(endpoint) = &config.s3_endpoint {
        builder = builder.endpoint_url(endpoint);
    }
    Client::from_conf(builder.build())
}

pub fn original_key(photo_id: Uuid, filename: &str) -> String {
    format!("photos/{photo_id}/original/{filename}")
}

pub fn preview_key(photo_id: Uuid) -> String {
    format!("photos/{photo_id}/preview.jpg")
}

pub fn thumb_key(photo_id: Uuid) -> String {
    format!("photos/{photo_id}/thumb.jpg")
}

pub fn photo_prefix(photo_id: Uuid) -> String {
    format!("photos/{photo_id}/")
}

pub async fn put_file(
    state: &AppState,
    key: &str,
    path: &Path,
    content_type: &str,
) -> anyhow::Result<()> {
    let body = ByteStream::from_path(path).await?;
    state
        .s3
        .put_object()
        .bucket(&state.config.s3_bucket)
        .key(key)
        .content_type(content_type)
        .body(body)
        .send()
        .await?;
    Ok(())
}

pub async fn put_bytes(
    state: &AppState,
    key: &str,
    bytes: Vec<u8>,
    content_type: &str,
) -> anyhow::Result<()> {
    state
        .s3
        .put_object()
        .bucket(&state.config.s3_bucket)
        .key(key)
        .content_type(content_type)
        .body(ByteStream::from(bytes))
        .send()
        .await?;
    Ok(())
}

pub async fn delete_prefix(state: &AppState, prefix: &str) -> anyhow::Result<()> {
    loop {
        let list = state
            .s3
            .list_objects_v2()
            .bucket(&state.config.s3_bucket)
            .prefix(prefix)
            .send()
            .await?;
        let objects: Vec<ObjectIdentifier> = list
            .contents()
            .iter()
            .filter_map(|o| o.key())
            .map(|k| ObjectIdentifier::builder().key(k).build())
            .collect::<Result<_, _>>()?;
        if objects.is_empty() {
            return Ok(());
        }
        state
            .s3
            .delete_objects()
            .bucket(&state.config.s3_bucket)
            .delete(Delete::builder().set_objects(Some(objects)).build()?)
            .send()
            .await?;
        if !list.is_truncated().unwrap_or(false) {
            return Ok(());
        }
    }
}
