use log::{debug, error, info};
use reqwest::{self};

use std::error::Error;
use std::time::Duration;

struct PixelfedConfig {
    pixelfed_url: String,
    pixelfed_access_token: String,
    pixelfed_visibility: String, // Should be "unlisted"
    pixelfed_default_text: String,
}

fn format_post_text(template: &str, batch_num: usize, total_batches: usize, title: &str) -> String {
    template
        .replace(
            "@batch@",
            &format!("Batch {} out of {}", batch_num, total_batches),
        )
        .replace("@title@", title)
}

// upload a single image to pixelfed
pub fn bulk_upload_images(
    config: &super::Config,
    images: &[String],
    batch_num: usize,
    total_batches: usize,
    title: &str,
    caption_mode: &super::Mode,
) -> Result<(), Box<dyn Error>> {
    let mut media_ids = Vec::new();
    let mut media_descriptions = Vec::new();

    // generate our Pixelfed specific Configuration from the given global config
    let pxl_config = PixelfedConfig {
        pixelfed_url: config.pixelfed_url.clone(),
        pixelfed_access_token: config.pixelfed_access_token.clone(),
        pixelfed_visibility: config.pixelfed_visibility.clone(),
        pixelfed_default_text: config.pixelfed_default_text.clone(),
    };

    let client = match reqwest::blocking::ClientBuilder::new()
        .connect_timeout(Duration::new(30, 0))
        .timeout(Duration::new(300, 0))
        .connection_verbose(true)
        .build()
    {
        Ok(client) => client,
        Err(e) => {
            error!("Failed to build HTTP client: {}", e);
            return Err(Box::from(e));
        }
    };
    // construct the full URL for the Pixelfed Upload
    let url = format!("{}/api/v1/media", pxl_config.pixelfed_url.clone());

    // Iterate over all the images we were given
    for image_path in images {
        let description: String;
        debug!("Handling image {}", &image_path.to_string());
        // get image description depending on the caption_mode
        match caption_mode {
            super::Mode::ChatGPT => {
                let im_config = super::image_description::ChatGPTConfig {
                    openai_model: config.openai_model.clone(),
                    openai_api_key: config.openai_api_key.clone(),
                    openai_api_url: config.openai_api_url.clone(),
                    openai_prompt: config.prompt.clone(),
                };
                info!(
                    "Fetching image description from ChatGPT for {}",
                    &image_path.to_string()
                );
                description = match super::image_description::get_description_from_chatgpt(
                    image_path.clone().to_string(),
                    im_config,
                ) {
                    Ok(description) => description,
                    Err(e) => {
                        error!("Failed to fetch image description from ChatGPT for {}", e);
                        return Err(Box::from(e));
                    }
                };
                media_descriptions.push(description.clone());
            }
            super::Mode::File => {
                let im_config = super::image_description::FileConfig {
                    caption_extension: config.caption_extension.clone(),
                };
                info!(
                    "Fetching image description from File for {}",
                    &image_path.to_string()
                );
                description = match super::image_description::get_description_from_file(
                    image_path.clone().to_string(),
                    im_config,
                ) {
                    Ok(description) => description,
                    Err(e) => {
                        error!("Failed to fetch image description from File for {}", e);
                        return Err(Box::from(e));
                    }
                };
                info!("Description generated by ChatGPT: {}", &description.clone());
                media_descriptions.push(description.clone());
            }
            super::Mode::Local => {
                let im_config = super::image_description::OllamaConfig {
                    ollama_api_key: config.ollama_api_key.clone(),
                    ollama_api_url: config.ollama_api_url.clone(),
                    ollama_model: config.ollama_model.clone(),
                    ollama_prompt: config.prompt.clone(),
                };
                info!(
                    "Fetching image description from OLLAMA for {}",
                    &image_path.to_string()
                );
                description = match super::image_description::get_description_from_ollama(
                    image_path.clone().to_string(),
                    im_config,
                ) {
                    Ok(description) => description,
                    Err(e) => {
                        error!("Failed to fetch image description from OLLAMA for {}", e);
                        return Err(Box::from(e));
                    }
                };
                info!("Description generated by OLLAMA: {}", &description.clone());
                media_descriptions.push(description.clone());
            }
            super::Mode::None => {
                // No impage description wanted
                description = "".to_string();
                media_descriptions.push(description.clone())
            }
        }

        println!("Uploading image {} to Pixelfed", &image_path.to_string());

        // construct the upload form for Pixelfed Upload of a single image including image description
        let form = match reqwest::blocking::multipart::Form::new()
            .text("description", description.clone())
            .file("file", image_path)
        {
            Ok(f) => f,
            Err(e) => {
                error!("Failed to construct multipart form: {}", e);
                return Err(Box::from(e));
            }
        };

        // upload the form to Pixelfed
        let res = match client
            .post(&url)
            .bearer_auth(&pxl_config.pixelfed_access_token)
            .multipart(form)
            .send()
        {
            Ok(result) => result,
            Err(e) => {
                error!("Failed to send request: {}", e);
                return Err(Box::from(e));
            }
        };

        let status = res.status();

        // Check if the response status indicates success
        if status.is_success() {
            // Parse the success response
            let success_response: serde_json::Value = res.json()?;
            let image_id = success_response["id"].clone();
            media_ids.push(image_id);
        } else {
            let error_text = res.text()?;
            return Err(Box::from(format!("Failed to upload image: {}", error_text)));
        }
    }

    println!("Done uploading all images: {}", media_ids.len());

    if !media_ids.is_empty() {
        let post_url = format!("{}/api/v1/statuses", pxl_config.pixelfed_url);
        let post_text = format_post_text(
            &pxl_config.pixelfed_default_text,
            batch_num,
            total_batches,
            title,
        );

        let body = serde_json::json!({
            "status": post_text.to_string(),
            "media_ids": media_ids,
            "alt_texts": media_descriptions,
            "visibility": pxl_config.pixelfed_visibility.to_string(),
        });
        info!("Body: \n{}", &body.to_string());
        info!("MediaIDs: {}", &media_ids.len());
        info!("Alt_texts: {}", &media_descriptions.len());
        println!(
            "Posting batch {} out of {} with media {}",
            batch_num,
            total_batches,
            media_ids.len()
        );
        let res = client
            .post(&post_url)
            .bearer_auth(&pxl_config.pixelfed_access_token)
            .json(&body)
            .send();
        match res {
            Ok(response) => {
                if response.status().is_success() {
                    info!("Post created successfully!");
                } else {
                    error!("Failed to create post. Status: {:?}", response.status());
                    let error_message = response
                        .text()
                        .unwrap_or_else(|_| "Unknown error".to_string());
                    error!("Error message: {}", error_message);
                }
            }
            Err(e) => {
                error!("Failed to send request: {}", e);
            }
        }
    }
    Ok(())
}
