//! Example: Generate songs, poll for completion, and download audio files
//!
//! This example demonstrates the full generation workflow:
//! 1. Submit generation requests to Suno API
//! 2. Poll for completion with exponential backoff
//! 3. Download completed audio files to disk
//!
//! Prerequisites:
//! - Set SUNO_BASE_URL in .env (e.g., http://localhost:8000 or production URL)
//! - Set SUNO_API_KEY in .env
//!
//! Run with:
//!   cargo run --example generate_and_download

use std::fs;
use std::path::PathBuf;
use std::time::Duration;

use dotenvy::dotenv;
use reqwest::blocking::Client;
use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_TYPE};
use sunocore::models::generation::{GenerateParams, GenerationResponse};
use sunocore::validation::env::{suno_api_key_unwrap, suno_base_url_unwrap};

/// Create an HTTP client with authentication headers
fn create_client(api_key: &str) -> Client {
    let mut headers = HeaderMap::new();
    headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
    headers.insert(
        AUTHORIZATION,
        HeaderValue::from_str(&format!("Bearer {}", api_key)).expect("valid header"),
    );
    headers.insert(
        "X-Api-Key",
        HeaderValue::from_str(api_key).expect("valid header"),
    );

    Client::builder()
        .default_headers(headers)
        .timeout(Duration::from_secs(300)) // 5 min for long generation
        .build()
        .expect("build http client")
}

/// Submit a generation request
fn submit_generation(
    client: &Client,
    base_url: &str,
    params: &GenerateParams,
) -> Result<GenerationResponse, Box<dyn std::error::Error>> {
    let url = format!("{}/api/generate/v2", base_url.trim_end_matches('/'));

    println!("Submitting generation request to {}", url);
    println!("  Prompt: {}", params.prompt);
    if let Some(ref title) = params.title {
        println!("  Title: {}", title);
    }

    let resp = client.post(&url).json(params).send()?;

    let status = resp.status();
    let body = resp.text()?;

    if !status.is_success() {
        return Err(format!("HTTP {}: {}", status, &body[..body.len().min(200)]).into());
    }

    let gen_response: GenerationResponse = serde_json::from_str(&body)?;

    println!("✓ Generation submitted successfully");
    println!("  Request ID: {}", gen_response.id);
    println!("  Status: {}", gen_response.status);
    println!("  Clips: {}", gen_response.clips.len());

    Ok(gen_response)
}

/// Poll feed to find clip status with exponential backoff
/// Uses the feed endpoint which is designed for polling
fn poll_feed_for_clip(
    client: &Client,
    base_url: &str,
    workspace_id: uuid::Uuid,
    clip_id: &str,
    max_attempts: u32,
) -> Result<sunocore::models::feed::Clip, Box<dyn std::error::Error>> {
    use sunocore::models::feed::{FeedFilters, FeedRequest, FeedResponse, PresenceFilter, WorkspaceFilter};

    let url = format!("{}/api/feed/v3", base_url.trim_end_matches('/'));

    println!("\nPolling feed for clip: {}", clip_id);

    let mut backoff_ms = 1000u64;
    let backoff_multiplier = 1.5;
    let max_backoff_ms = 30000u64;

    let feed_request = FeedRequest {
        cursor: None,
        limit: 100, // Get more clips to increase chance of finding our new ones
        filters: FeedFilters {
            disliked: "False".to_string(),
            trashed: "False".to_string(),
            from_studio_project: PresenceFilter {
                presence: "any".to_string(), // Changed to "any" to be less restrictive
            },
            stem: PresenceFilter {
                presence: "any".to_string(), // Changed to "any"
            },
            workspace: WorkspaceFilter {
                presence: "True".to_string(),
                workspace_id,
            },
        },
    };

    for attempt in 1..=max_attempts {
        let resp = client.post(&url).json(&feed_request).send()?;
        let status = resp.status();
        let body = resp.text()?;

        if !status.is_success() {
            println!("  Attempt {}: HTTP {}", attempt, status);
            std::thread::sleep(Duration::from_millis(backoff_ms));
            backoff_ms = ((backoff_ms as f64 * backoff_multiplier) as u64).min(max_backoff_ms);
            continue;
        }

        let feed_response: FeedResponse = serde_json::from_str(&body)?;

        // Debug: show first few clips in feed on first attempt
        if attempt == 1 {
            println!("  Feed has {} clips", feed_response.clips.len());
            for (i, c) in feed_response.clips.iter().take(3).enumerate() {
                println!("    Feed[{}]: {} - {}", i, c.id, c.title);
            }
        }

        // Look for our clip in the feed
        if let Some(clip) = feed_response.clips.iter().find(|c| c.id.to_string() == clip_id) {
            print!("  Attempt {}: Found in feed, status={} ", attempt, clip.status);

            // Check if audio_url is available
            if !clip.audio_url.is_empty() {
                println!("✓");
                println!("  Audio URL: {}...", &clip.audio_url[..clip.audio_url.len().min(60)]);
                return Ok(clip.clone());
            } else {
                println!("(no audio yet)");
            }
        } else {
            print!("  Attempt {}: Not in feed yet ", attempt);
        }

        println!("(waiting {}ms)", backoff_ms);
        std::thread::sleep(Duration::from_millis(backoff_ms));
        backoff_ms = ((backoff_ms as f64 * backoff_multiplier) as u64).min(max_backoff_ms);
    }

    Err("Max polling attempts exceeded".into())
}

/// Download audio file from URL to local disk
fn download_audio(
    _client: &Client,
    audio_url: &str,
    output_path: &PathBuf,
) -> Result<(), Box<dyn std::error::Error>> {
    println!("\nDownloading audio to {:?}", output_path);

    // Download without auth headers (CDN URLs are public)
    let simple_client = Client::builder()
        .timeout(Duration::from_secs(60))
        .build()?;

    let resp = simple_client.get(audio_url).send()?;

    if !resp.status().is_success() {
        return Err(format!("Download failed: {}", resp.status()).into());
    }

    let bytes = resp.bytes()?;
    fs::write(output_path, &bytes)?;

    println!("✓ Downloaded {} bytes", bytes.len());
    Ok(())
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Load environment variables
    let _ = dotenv();

    let base_url = suno_base_url_unwrap();
    let api_key = suno_api_key_unwrap().ok_or("SUNO_API_KEY not set")?;
    let workspace_id = sunocore::validation::env::workspace_id_unwrap();

    println!("=== Suno Generation & Download Example ===");
    println!("Base URL: {}", base_url);
    println!("Workspace ID: {}", workspace_id);
    println!();

    let client = create_client(&api_key);

    // Create output directory
    let output_dir = PathBuf::from("/Users/neil/suno/oxide/target/generated_songs");
    fs::create_dir_all(&output_dir)?;
    println!("Output directory: {:?}", output_dir);
    println!();

    // Define songs to generate
    let songs = vec![
        GenerateParams::new("a short upbeat lo-fi hip hop beat with smooth jazz samples")
            .with_title("Lo-Fi Study Beat"),
        GenerateParams::new("an energetic 80s synthwave track with retro drums")
            .with_title("Neon Nights"),
        GenerateParams::new("a calm ambient soundscape with nature sounds")
            .with_title("Forest Dawn")
            .with_instrumental(true),
    ];

    println!("Generating {} songs...\n", songs.len());

    let mut completed_clips: Vec<sunocore::models::feed::Clip> = Vec::new();

    for (idx, params) in songs.iter().enumerate() {
        println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
        println!("Song {}/{}: {}", idx + 1, songs.len(), params.title.as_ref().unwrap());
        println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");

        // Submit generation
        match submit_generation(&client, &base_url, params) {
            Ok(gen_response) => {
                println!("✓ Submitted, waiting for clips to appear in feed...");

                // Poll feed for each clip to get audio URLs
                for clip in gen_response.clips {
                    match poll_feed_for_clip(&client, &base_url, workspace_id, &clip.id, 30) {
                        Ok(feed_clip) => {
                            println!("✓ Clip ready: {}", feed_clip.id);
                            completed_clips.push(feed_clip);
                        }
                        Err(e) => {
                            eprintln!("✗ Failed to find clip in feed: {}", e);
                        }
                    }
                }
            }
            Err(e) => {
                eprintln!("✗ Generation submission failed: {}", e);
            }
        }

        println!();
    }

    println!("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    println!("Downloading {} completed clips", completed_clips.len());
    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");

    for (idx, clip) in completed_clips.iter().enumerate() {
        let audio_url = &clip.audio_url;

        if !audio_url.is_empty() {
            let title = clip.title.replace(" ", "_").replace("/", "_");
            let filename = format!("{:02}_{}.mp3", idx + 1, title);
            let output_path = output_dir.join(filename);

            match download_audio(&client, audio_url, &output_path) {
                Ok(_) => {
                    println!("✓ Downloaded: {}", output_path.file_name().unwrap().to_str().unwrap());
                }
                Err(e) => {
                    eprintln!("✗ Download failed for {}: {}", clip.id, e);
                }
            }
        } else {
            eprintln!("✗ No audio URL for clip {}", clip.id);
        }
    }

    println!("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    println!("✓ COMPLETE!");
    println!("  Total songs generated: {}", completed_clips.len());
    println!("  Output directory: {:?}", output_dir);
    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");

    Ok(())
}
