//! ngemsite deployment

use super::SiteManifest;
use crate::config::SiteConfig;
use crate::error::ServiceError;
use colored::*;
use nostr::hashes::Hash;
use nostr::hashes::sha256::Hash as Sha256Hash;
use nostr_blossom::client::BlossomClient;
use nostr_sdk::prelude::*;
use std::fs;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::sync::Arc;

use super::gemtext_to_html;

pub async fn site_preprocess(
    path: &PathBuf,
    config: &SiteConfig,
) -> Result<(), ServiceError> {
    let entries = fs::read_dir(&path)?;

    for entry in entries {
        let entry = entry?;
        let Ok(file_type) = entry.file_type() else {
            continue;
        };
        if !file_type.is_file() {
            continue;
        }

        // Automatically convert gemtext documents to html, if enabled
        if let Some(ext) = entry.path().extension()
            && ext == "gmi"
            && config.auto_gemtext_to_html
        {
            let text = tokio::fs::read_to_string(entry.path()).await?;
            let html = gemtext_to_html(&text);

            let mut html_path = entry.path().clone();
            html_path.set_extension("html");

            let mut html_file = File::create(&html_path)?;
            html_file.write_all(&html.into_bytes())?;
        }
    }

    Ok(())
}

/// Deploy a ngemsite with the passed signer
pub async fn site_deploy(
    path: PathBuf,
    config: SiteConfig,
    signer: Arc<dyn NostrSigner>,
    force: bool,
) -> Result<SiteManifest, ServiceError> {
    let public_dir = path.join("public");

    // Preprocess
    site_preprocess(&public_dir, &config).await?;

    let client = Client::builder().signer(signer.clone()).build();

    let mut manifest = SiteManifest::default()
        .with_title(config.title)
        .with_description(config.description);

    let mut upload_count: usize = 0;

    if let Some(source) = config.source {
        manifest = manifest.with_source(source);
    }

    manifest.servers = config.servers.clone();
    manifest.relays = config.relays.clone();

    // Add relays
    for relay in &config.relays {
        client.add_relay(relay).await?;
    }

    client.connect().await;

    // Upload every file to every blossom server
    let entries = fs::read_dir(&public_dir)?;
    for entry in entries {
        let entry = entry?;
        let Ok(file_type) = entry.file_type() else {
            continue;
        };

        let m_path = entry.path();

        if !file_type.is_file() {
            continue;
        }

        let m_path = m_path.strip_prefix(&public_dir)?;
        let m_path = format!("/{}", m_path.display());

        let data = tokio::fs::read(entry.path()).await?;
        let data_hash = Sha256Hash::hash(&data);

        for server in &config.servers {
            let blossom = BlossomClient::new(server.clone());

            if blossom.has_blob(data_hash, None, Some(&signer)).await? {
                // Server already has a blob with that hash, don't reupload
                println!(
                    "=> {} {}",
                    "Not reuploading".yellow(),
                    m_path.blue().bold()
                );

                manifest.add_file(m_path.clone(), data_hash);
                continue;
            }

            match blossom
                .upload_blob(data.clone(), None, None, Some(&signer))
                .await
            {
                Ok(blob_descriptor) => {
                    manifest.add_file(m_path.clone(), blob_descriptor.sha256);

                    upload_count += 1;
                }
                Err(_) => {
                    eprintln!(
                        "Error uploading file {} to {}",
                        entry.path().display(),
                        server
                    );
                }
            }
        }
    }

    if upload_count == 0 && !force {
        // No new files were uploaded, don't send a new manifest for nothing
        return Err(ServiceError::NotSendingManifest);
    }

    match client
        .send_event_builder_to(config.relays, manifest.clone().into())
        .await
    {
        Ok(_result) => Ok(manifest),
        Err(_) => Err(ServiceError::CannotSendManifest),
    }
}
