//! Integration tests for discover and search endpoints
//!
//! These tests call real SUNO_BASE_URL and fetch data. They ALWAYS RUN against
//! the real API - no mocking, no ignoring.
//!
//! Required setup:
//!   export SUNO_BASE_URL="http://127.0.0.1:8000"
//!   export SUNO_API_KEY="your-api-key-here"
//!
//! Run with: cargo nextest run --test integration_discover_search
//! Or:       cargo test --test integration_discover_search

use dotenvy::dotenv;
use reqwest::blocking::Client;
use sunocore::models::discover::{DiscoverRequest, DiscoverResponse};
use sunocore::models::search::{
    HashtagSearchResponse, HashtagTrendingRequest, OmnisearchRequest, OmnisearchResponse,
    SearchFilters, SearchQuery, SearchRequest, SearchResponse, SearchType,
};
use sunocore::validation::env::{suno_api_key_unwrap, suno_base_url_unwrap};

#[test]
fn test_get_homepage_sections_real_api() {
    let _ = dotenv();
    let base_url = suno_base_url_unwrap();
    let api_key = suno_api_key_unwrap();
    let client = Client::new();

    let discover_req = DiscoverRequest::default();
    let resp = client
        .post(format!("{}/api/discover", base_url))
        .header("Content-Type", "application/json")
        .header(
            "Authorization",
            format!("Bearer {}", api_key.clone().unwrap_or_default()),
        )
        .json(&discover_req)
        .send()
        .expect("Failed to send request");

    assert!(
        resp.status().is_success(),
        "API returned error: {}",
        resp.status()
    );

    let body_text = resp.text().expect("Failed to read response body");
    let discover_resp: DiscoverResponse =
        serde_json::from_str(&body_text).expect("Failed to deserialize response");

    eprintln!("✓ Homepage sections fetched successfully");
    eprintln!("  Sections count: {}", discover_resp.sections.len());
    eprintln!("  Total sections: {}", discover_resp.total_sections);

    // Print first section for use in other tests
    if let Some(first_section) = discover_resp.sections.first() {
        match first_section {
            sunocore::models::discover::DiscoverSection::Playlist(p) => {
                eprintln!("  First section: PlaylistSection - {}", p.base.id);
            }
            sunocore::models::discover::DiscoverSection::PlaylistList(p) => {
                eprintln!(
                    "  First section: PlaylistListSection - {} items",
                    p.items.len()
                );
                if let Some(first_item) = p.items.first() {
                    eprintln!(
                        "    First playlist ID: {} (save for Phase 3)",
                        first_item.id
                    );
                }
            }
            _ => eprintln!("  First section: Other type"),
        }
    }

    assert!(discover_resp.sections.len() > 0 || discover_resp.total_sections > 0);
}

#[test]
fn test_search_public_songs_real_api() {
    let _ = dotenv();
    let base_url = suno_base_url_unwrap();
    let api_key = suno_api_key_unwrap();
    let client = Client::new();

    let search_query = SearchQuery {
        name: "public_songs_search".to_string(),
        search_type: SearchType::PublicSong,
        item_type: None,
        term: Some("ambient".to_string()),
        genre: None,
        vector: None,
        user_id: None,
        project_id: None,
        song_id: None,
        is_public: Some(true),
        is_liked: None,
        is_suno_short: None,
        filters: None,
        rank_by: Some("most_recent".to_string()),
        order: None,
        user_ids: None,
        boosted_user_handles: None,
        languages: None,
        from_index: 0,
        size: 10,
        exclude_song_ids: None,
        is_instrumental: None,
        model_version: None,
        maximum_create_time: None,
    };

    let search_req = SearchRequest {
        search_queries: vec![search_query],
    };

    let resp = client
        .post(format!("{}/api/search", base_url))
        .header("Content-Type", "application/json")
        .header(
            "Authorization",
            format!("Bearer {}", api_key.clone().unwrap_or_default()),
        )
        .json(&search_req)
        .send()
        .expect("Failed to send request");

    assert!(
        resp.status().is_success(),
        "API returned error: {}",
        resp.status()
    );

    let body_text = resp.text().expect("Failed to read response body");
    let search_resp: SearchResponse =
        serde_json::from_str(&body_text).expect("Failed to deserialize response");

    eprintln!("✓ Search for public songs successful");
    for (query_name, search_result) in search_resp.result.iter() {
        eprintln!("  Query: {}", query_name);
        eprintln!("    Total hits: {}", search_result.total_hits);
        eprintln!("    Results returned: {}", search_result.result.len());

        // Extract clip IDs for use in other tests
        for (idx, item) in search_result.result.iter().take(3).enumerate() {
            if let sunocore::models::search::SearchResultItem::Song(clip) = item {
                eprintln!(
                    "    Clip {}: ID={}, Title={} (save for Phase 2)",
                    idx, clip.id, clip.title
                );
            }
        }
    }

    assert!(search_resp.result.len() > 0);
}

#[test]
fn test_search_users_real_api() {
    let _ = dotenv();
    let base_url = suno_base_url_unwrap();
    let api_key = suno_api_key_unwrap();
    let client = Client::new();

    let user_search_req = sunocore::models::search::UserSearchRequest {
        term: "test".to_string(),
        boosted_user_handles: None,
        excluded_user_handles: None,
    };

    let resp = client
        .post(format!("{}/api/search/users", base_url))
        .header("Content-Type", "application/json")
        .header(
            "Authorization",
            format!("Bearer {}", api_key.unwrap_or_default()),
        )
        .json(&user_search_req)
        .send()
        .expect("Failed to send request");

    assert!(
        resp.status().is_success(),
        "API returned error: {}",
        resp.status()
    );

    let body_text = resp.text().expect("Failed to read response body");
    let users: Vec<sunocore::models::search::SearchProfile> =
        serde_json::from_str(&body_text).expect("Failed to deserialize response");

    eprintln!("✓ User search successful");
    eprintln!("  Users found: {}", users.len());

    for (idx, user) in users.iter().take(3).enumerate() {
        eprintln!(
            "  User {}: {} ({}) - save handle for Phase 2",
            idx,
            user.display_name.as_deref().unwrap_or(&user.handle),
            user.handle
        );
    }

    assert!(users.len() > 0);
}

#[test]
fn test_hashtag_trending_real_api() {
    let _ = dotenv();
    let base_url = suno_base_url_unwrap();
    let api_key = suno_api_key_unwrap();
    let client = Client::new();

    let payload = HashtagTrendingRequest {
        days: Some(7),
        size: Some(10),
    };

    let resp = client
        .post(format!("{}/api/search/hashtag_trending", base_url))
        .header("Content-Type", "application/json")
        .header(
            "Authorization",
            format!("Bearer {}", api_key.clone().unwrap_or_default()),
        )
        .json(&payload)
        .send()
        .expect("Failed to send request");

    assert!(
        resp.status().is_success(),
        "API returned error: {}",
        resp.status()
    );

    let body_text = resp.text().expect("Failed to read response body");
    let trending: HashtagSearchResponse =
        serde_json::from_str(&body_text).expect("Failed to deserialize response");

    eprintln!("✓ Trending hashtags fetched successfully");
    eprintln!("  Hashtags returned: {}", trending.hashtags.len());

    // Note: The API may return 0 hashtags if there are no trending hashtags at this time
    // This is a valid response, so we just verify the response was processed correctly
}

#[test]
fn test_omnisearch_real_api() {
    let _ = dotenv();
    let base_url = suno_base_url_unwrap();
    let api_key = suno_api_key_unwrap();
    let client = Client::new();

    let payload = OmnisearchRequest {
        term: "ambient".to_string(),
    };

    let resp = client
        .post(format!("{}/api/search/omnisearch", base_url))
        .header("Content-Type", "application/json")
        .header(
            "Authorization",
            format!("Bearer {}", api_key.clone().unwrap_or_default()),
        )
        .json(&payload)
        .send()
        .expect("Failed to send request");

    assert!(
        resp.status().is_success(),
        "API returned error: {}",
        resp.status()
    );

    let body_text = resp.text().expect("Failed to read response body");
    let omnisearch: OmnisearchResponse =
        serde_json::from_str(&body_text).expect("Failed to deserialize response");

    eprintln!("✓ Omnisearch successful");
    eprintln!("  Sections returned: {}", omnisearch.sections.len());

    assert!(
        omnisearch
            .sections
            .iter()
            .any(|section| !section.result.is_empty()),
        "expected at least one section with results"
    );
}

#[test]
fn test_search_with_new_ergonomic_api() {
    let _ = dotenv();
    let base_url = suno_base_url_unwrap();
    let api_key = suno_api_key_unwrap();
    let client = Client::new();

    eprintln!("\n=== Testing New Ergonomic Search API ===\n");

    // Test 1: Simple search using new builder API
    eprintln!("Test 1: Simple public song search");
    let search_req = SearchRequest::simple_public_song("ambient").expect("Failed to build request");
    let resp = client
        .post(format!("{}/api/search", base_url))
        .header("Content-Type", "application/json")
        .header(
            "Authorization",
            format!("Bearer {}", api_key.clone().unwrap_or_default()),
        )
        .json(&search_req)
        .send()
        .expect("Failed to send request");

    assert!(resp.status().is_success());
    let search_resp: SearchResponse = resp.json().expect("Failed to parse response");
    eprintln!(
        "✓ Simple search successful, found {} queries in response",
        search_resp.result.len()
    );

    // Test 2: Compound search with multiple queries using builder
    eprintln!("\nTest 2: Compound search with multiple query types");
    let search_req = SearchRequest::builder()
        .public_song("jazz")
        .user("artist")
        .build()
        .expect("Failed to build request");

    assert_eq!(search_req.search_queries.len(), 2);
    eprintln!(
        "✓ Built compound request with {} queries",
        search_req.search_queries.len()
    );

    let resp = client
        .post(format!("{}/api/search", base_url))
        .header("Content-Type", "application/json")
        .header(
            "Authorization",
            format!("Bearer {}", api_key.clone().unwrap_or_default()),
        )
        .json(&search_req)
        .send()
        .expect("Failed to send request");

    assert!(resp.status().is_success());
    let search_resp: SearchResponse = resp.json().expect("Failed to parse response");
    eprintln!(
        "✓ Compound search successful, got {} results",
        search_resp.result.len()
    );

    // Test 3: Search with filters using builder
    eprintln!("\nTest 3: Search with filters");
    let filters = SearchFilters::builder().full_song().no_covers().build();

    let query = SearchQuery::public_song("ambient")
        .filters(filters)
        .is_instrumental(true)
        .rank_by("most_recent")
        .size(5)
        .build()
        .expect("Failed to build query");

    let search_req = SearchRequest::builder()
        .add_query(query)
        .build()
        .expect("Failed to build request");

    eprintln!(
        "✓ Built filtered query with name: {}",
        search_req.search_queries[0].name
    );

    let resp = client
        .post(format!("{}/api/search", base_url))
        .header("Content-Type", "application/json")
        .header(
            "Authorization",
            format!("Bearer {}", api_key.clone().unwrap_or_default()),
        )
        .json(&search_req)
        .send()
        .expect("Failed to send request");

    assert!(resp.status().is_success());
    eprintln!("✓ Filtered search successful");

    // Test 4: Similar songs search
    eprintln!("\nTest 4: Similar songs search (requires valid song ID)");
    let query = SearchQuery::similar_to("550e8400-e29b-41d4-a716-446655440000") // Example UUID
        .size(10)
        .build()
        .expect("Failed to build query");

    let search_req = SearchRequest::builder()
        .add_query(query)
        .build()
        .expect("Failed to build request");

    eprintln!(
        "✓ Built similar song query: {}",
        search_req.search_queries[0].name
    );

    eprintln!("\n=== All new API tests passed! ===\n");
}
