use base64::{engine::general_purpose::STANDARD, Engine as _};
use log::{debug, error, info};
use serde::Deserialize;
use serde::Serialize;
use serde_json::json;
use std::str;
use std::time::Duration;

// module to hold all code for generating/fetching image descriptions
// Input is the image name
// Output is a String containing the image description
pub struct ChatGPTConfig {
    pub openai_api_key: String,
    pub openai_api_url: String,
    pub openai_model: String,
    pub openai_prompt: String,
}

pub struct OllamaConfig {
    pub ollama_api_key: String,
    pub ollama_api_url: String,
    pub ollama_model: String,
    pub ollama_prompt: String,
}

pub struct FileConfig {
    pub caption_extension: String,
}

#[derive(Serialize, Deserialize)]
struct LlamaModel {
    model: String,
    prompt: String,
    stream: bool,
    format: String,
    suffix: String,
    images: Vec<String>,
    keep_alive: i8,
}
// fetch the imagedescription from a file named like the Image
pub fn get_description_from_file(
    image_name: String,
    file_config: FileConfig,
) -> Result<String, Box<dyn std::error::Error>> {
    //read image caption from a local file that
    //has the same name than the image with the extension ".caption.txt"
    let caption_extension = file_config.caption_extension;
    let captionname = format!("{}{}", &image_name, caption_extension);

    debug!("Looking for {}", &captionname);
    let caption_data = match std::fs::read_to_string(captionname) {
        Ok(caption) => caption,
        Err(e) => {
            error!("Failed to find caption file: {}", &e.to_string());
            std::process::exit(1);
        }
    };

    println!(
        "Description fetched successfully from FILE for {}",
        &image_name
    );

    Ok(caption_data)
}

// fetch image description from ChatGPT
pub fn get_description_from_chatgpt(
    image_name: String,
    chatgpt_config: self::ChatGPTConfig,
) -> Result<String, Box<dyn super::Error>> {
    // Read and encode image
    let image_data = std::fs::read(&image_name)?;

    // Base64 encode the image for ChatGTP API
    let base64_image = STANDARD.encode(image_data);

    // Create ChatGPT API request
    let client = reqwest::blocking::Client::new();
    let response = client
        .post(chatgpt_config.openai_api_url)
        .header(
            "Authorization",
            format!("Bearer {}", chatgpt_config.openai_api_key),
        )
        .header("Content-Type", "application/json")
        .json(&super::json!({
        "model": chatgpt_config.openai_model,
        "max_tokens": 300,
        "messages": [
            {
                "role": "user",
                "content": [
                    {
                        "type": "text",
                        "text": chatgpt_config.openai_prompt.to_string(),
                    },
                    {
                        "type": "image_url",
                        "image_url": {
                            "url": format!("data:image/jpeg;base64,{}", base64_image)
                        }
                        }
                        ]
                        }
                        ]
                        }))
        .send();

    // Improved error handling for API response
    //if !response.unwrap().status().is_success() {
    //    let error_text = response.unwrap_err();
    //    return Err(format!("OpenAI API error: ", std::error.box(error_text));
    //}

    let result: super::Value = response.unwrap().json()?;

    // More detailed error handling for JSON parsing
    let description = result["choices"].get(0).ok_or("No choices in response")?["message"]
        ["content"]
        .as_str()
        .ok_or("Invalid content format in response")?
        .to_string();
    println!(
        "Description generated successfully from ChatGPT for {}",
        &image_name
    );

    Ok(description)
}

// fetch images description from own OLLAMA server
pub fn get_description_from_ollama(
    image_name: String,
    ollama_config: OllamaConfig,
) -> Result<String, Box<dyn super::Error>> {
    // Read and encode image
    let image_data = std::fs::read(&image_name)?;
    // Base64 encode the image for ChatGTP API
    let base64_image = STANDARD.encode(image_data);

    // Create the JSON payload
    let payload = json!({
        "model": ollama_config.ollama_model.to_string(),
        "prompt": ollama_config.ollama_prompt.to_string(),
        "stream": false,
        "images": [base64_image]
    });

    debug!("Payload for image OLLAMA API: \n{}", payload.clone());
    // println!("JSON output:\n{}", json.clone());
    // Create ChatGPT API request
    // let client = reqwest::blocking::Client::new();
    let client = reqwest::blocking::ClientBuilder::new()
        .connect_timeout(Duration::new(30, 0))
        .timeout(Duration::new(300, 0))
        .connection_verbose(true)
        .build()?;

    let response = client
        .post(ollama_config.ollama_api_url)
        .header("Content-Type", "application/json")
        .json(&payload)
        .send();

    let response = match response {
        Ok(response) => {
            info!("success!");
            response
        }
        Err(e) => {
            error!("Failed to send request to OLLAMA: {}", e);
            return Err(Box::from(e));
        }
    };

    // Extract response text
    let result: super::Value = response.json()?;
    let description = if let Some(response_text) = result["response"].as_str() {
        response_text.replace("\\n", "\n").replace("\\\"", "\"")
    } else if let Some(error_text) = result["error"].as_str() {
        error_text.replace("\\n", "\n").replace("\\\"", "\"")
    } else {
        "Could not find response or error from OLLAMA".to_string()
    };

    info!("Description generated by OLLAMA: {}", &description);
    println!(
        "Description generated successfully from OLLAMA for {}",
        &image_name
    );
    Ok(description)
}
