//! S3 upload for publication binaries (PDF / HTML).

use anyhow::{anyhow, Result};
use aws_sdk_s3::primitives::ByteStream;

/// Env-backed settings for [`PublicationS3Service`].
#[derive(Debug, Clone)]
pub struct PublicationS3Config {
    pub bucket: String,
    pub prefix: String,
    /// When true, skip upload if object already exists (`head_object`).
    pub skip_existing: bool,
}

impl PublicationS3Config {
    pub fn from_env() -> Option<Self> {
        let bucket = std::env::var("S3_PUBLICATIONS_BUCKET").ok()?;
        let mut prefix =
            std::env::var("S3_PUBLICATIONS_PREFIX").unwrap_or_else(|_| "publications/pwc-al/".to_string());
        if !prefix.ends_with('/') {
            prefix.push('/');
        }
        let skip_existing = std::env::var("S3_PUBLICATIONS_SKIP_EXISTING")
            .map(|s| {
                let l = s.to_lowercase();
                l != "false" && l != "0"
            })
            .unwrap_or(true);
        Some(Self {
            bucket,
            prefix,
            skip_existing,
        })
    }
}

pub struct PublicationS3Service {
    s3: aws_sdk_s3::Client,
    config: PublicationS3Config,
}

impl PublicationS3Service {
    pub async fn from_env() -> Option<Self> {
        let config = PublicationS3Config::from_env()?;
        let sdk = aws_config::load_from_env().await;
        let s3 = aws_sdk_s3::Client::new(&sdk);
        Some(Self { s3, config })
    }

    pub fn config(&self) -> &PublicationS3Config {
        &self.config
    }

    pub fn prefix(&self) -> &str {
        &self.config.prefix
    }

    pub async fn object_exists(&self, key: &str) -> Result<bool> {
        match self
            .s3
            .head_object()
            .bucket(&self.config.bucket)
            .key(key)
            .send()
            .await
        {
            Ok(_) => Ok(true),
            Err(e) => {
                let not_found = e
                    .as_service_error()
                    .map(|se| se.is_not_found())
                    .unwrap_or(false);
                if not_found {
                    Ok(false)
                } else {
                    let raw = format!("{}", e);
                    if raw.contains("404") {
                        Ok(false)
                    } else {
                        Err(anyhow!("S3 head_object: {}", e))
                    }
                }
            }
        }
    }

    pub async fn put_bytes(&self, key: &str, body: Vec<u8>, content_type: &str) -> Result<()> {
        self.s3
            .put_object()
            .bucket(&self.config.bucket)
            .key(key)
            .content_type(content_type)
            .body(ByteStream::from(body))
            .send()
            .await
            .map_err(|e| anyhow!("S3 put_object: {}", e))?;
        Ok(())
    }
}
