use std::fs;
use serde::Deserialize;
use serde_json::{json, Value};
use reqwest::blocking::Client;
use reqwest::{self};
use std::error::Error;
use base64::{Engine as _, engine::general_purpose::STANDARD};

#[derive(Debug, Deserialize)]
struct Config {
    pixelfed_url: String,
    access_token: String,
    visibility: String,  // Should be "unlisted"
    default_text: String,
    batch_size: usize,
    openai_api_key: String,
    openai_api_url: String,
    openai_model: String
    
}

fn load_config() -> Result<Config, Box<dyn Error>> {
    let config_str = fs::read_to_string("config.json")?;
    let config: Config = serde_json::from_str(&config_str)?;
    Ok(config)
}

fn get_jpeg_files(directory: &str) -> Vec<String> {
    let mut images = Vec::new();
    if let Ok(entries) = fs::read_dir(directory) {
        for entry in entries.flatten() {
            let path = entry.path();
            if let Some(ext) = path.extension() {
                if ext.eq_ignore_ascii_case("jpg") || ext.eq_ignore_ascii_case("jpeg") {
                    images.push(path.to_string_lossy().to_string());
                }
            }
        }
    }
    images
}

async fn get_image_description(config: &Config, image_path: &String) -> Result<String, Box<dyn Error>> {
    // Read and encode image
    let image_data = tokio::fs::read(image_path)
        .await
        .map_err(|e| format!("Failed to read image file: {}", e))?;
    
    let base64_image = STANDARD.encode(&image_data);

    // Create ChatGPT API request
    let client = reqwest::Client::new();
    let response = client
        .post(&config.openai_api_url)
        .header("Authorization", format!("Bearer {}", config.openai_api_key))
        .header("Content-Type", "application/json")
        .json(&json!({
            "model": config.openai_model,
            "max_tokens": 300,
            "messages": [
                {
                    "role": "user",
                    "content": [
                        {
                            "type": "text",
                            "text": "Please describe this image concisely for use as an alt text description. Focus on key visual elements and context."
                        },
                        {
                            "type": "image_url",
                            "image_url": {
                                "url": format!("data:image/jpeg;base64,{}", base64_image)
                            }
                        }
                    ]
                }
            ]
        }))
        .send()
        .await?;

    // Improved error handling for API response
    if !response.status().is_success() {
        let error_text = response.text().await?;
        return Err(format!("OpenAI API error: {}", error_text).into());
    }

    let result: Value = response.json().await?;
    
    // 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();

    Ok(description)
}


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)
}

async fn upload_images_batch(client: &Client, config: &Config, images: &[String], batch_num: usize, total_batches: usize, title: &str) -> Result<(), Box<dyn Error>> {
    let url = format!("{}/api/v1/media", config.pixelfed_url);
    let mut media_ids = Vec::new();
    let mut media_descriptions = Vec::new();

    for image_path in images {
        println!("Fetching image description from OpenAI for {}", image_path.to_string());
        let  image_description: String ;
        if !config.openai_api_key.is_empty() {
            image_description = get_image_description(&config, &image_path).await?;    
            println!("DESC:\n {} \n", &image_description);
            media_descriptions.push(image_description.clone());    
            
        } else {
            image_description = String::new();
        }
        
        
        println!("Uploading image {}", image_path.to_string());
        
        let form = reqwest::blocking::multipart::Form::new().text("description", image_description.clone())
            .file("file", image_path)?;
            
        let res = client.post(&url)
            .bearer_auth(&config.access_token)
            .multipart(form)
            .send()?;

        let json: serde_json::Value = res.json()?;
        if let Some(id) = json["id"].as_str() {
            media_ids.push(id.to_string());
        }
    }

    if !media_ids.is_empty() {
        let post_url = format!("{}/api/v1/statuses", config.pixelfed_url);
        let post_text = format_post_text(&config.default_text, batch_num, total_batches, title);
        let body = serde_json::json!({
            "status": post_text,
            "media_ids": media_ids,
            "alt_texts": media_descriptions,
            "visibility": config.visibility,
        });
        
        println!("Posting batch {} out of {} with media {}", batch_num, total_batches, media_ids.len());
        client.post(&post_url)
            .bearer_auth(&config.access_token)
            .json(&body)
            .send()?;
    }

    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let args: Vec<String> = std::env::args().collect();
    if args.len() < 2 {
        eprintln!("Usage: {} <directory> [--title <title>]", args[0]);
        std::process::exit(1);
    }

    let mut title = "".to_string();
    if let Some(index) = args.iter().position(|x| x == "--title") {
        if index + 1 < args.len() {
            title = args[index + 1].clone();
        }
    }

    let config = load_config()?;
    let images = get_jpeg_files(&args[1]);
    let client = Client::new();
    let total_batches = (images.len() + config.batch_size - 1) / config.batch_size;
    println!("Found a total of {} images to upload. Will take {} batches", images.len(), total_batches);
    for (i, chunk) in images.chunks(config.batch_size).enumerate() {
        upload_images_batch(&client, &config, chunk, i + 1, total_batches, &title).await?;
    }

    println!("All images uploaded successfully.");
    Ok(())
}