//! Search loading UI example with real Suno API calls - Optimized for instant startup
//!
//! This example uses standalone bevy_ecs (not the full Bevy engine) for sub-100ms startup time.
//! We only use the ECS for state management, not the full framework.
//!
//! Setup:
//!   export SUNO_BASE_URL="http://127.0.0.1:8000"
//!   export SUNO_API_KEY="your-api-key-here"
//!
//! Keys:
//! - Space: cycle through different search terms
//! - Q or Esc: quit
//!
//! Multi-file logging (logs directory in Application Support):
//!   main.log   - All application logs (debug level)
//!   api.log    - API call details (trace level)
//!   search.log - Search operations (debug level)
//!   error.log  - Errors only (error level only)

use bevy_ecs::prelude::*;
use crossterm::{
    event::{self, Event, KeyCode, KeyEventKind},
    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
    ExecutableCommand,
};
use ratatui::{
    layout::{Alignment, Constraint, Direction, Layout},
    style::Stylize,
    text::Line,
    widgets::{Block, Cell, Paragraph, Row, Table},
    Terminal,
};
use std::io::stdout;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
use sunocore::models::search::{SearchQuery, SearchRequest, SearchResponse, SearchResultItem};

// Logging setup
use color_eyre::eyre::Result;
use directories::ProjectDirs;
use lazy_static::lazy_static;
use std::path::PathBuf;
use tracing::{debug, error, info, instrument, span, trace, warn, Level};
use tracing_error::ErrorLayer;
use tracing_subscriber::{self, layer::SubscriberExt, util::SubscriberInitExt, Layer};

lazy_static! {
    static ref PROJECT_NAME: String = env!("CARGO_CRATE_NAME").to_uppercase().to_string();
    static ref LOG_FILE: String = format!("{}.log", env!("CARGO_PKG_NAME"));
}

fn project_directory() -> Option<ProjectDirs> {
    ProjectDirs::from("com", "suno", env!("CARGO_PKG_NAME"))
}

fn get_data_dir() -> PathBuf {
    let directory = if let Some(proj_dirs) = project_directory() {
        proj_dirs.data_local_dir().to_path_buf()
    } else {
        PathBuf::from(".").join(".data")
    };
    directory
}

fn initialize_logging() -> Result<PathBuf> {
    use std::fs::File;
    use std::sync::Mutex;
    use tracing_subscriber::filter::EnvFilter;

    let log_dir = get_data_dir().join("logs");
    std::fs::create_dir_all(&log_dir)?;

    // Determine base filter from environment or use default
    let base_filter = std::env::var("RUST_LOG").ok();

    // Layer 1: Main log - everything
    let main_filter = base_filter.clone().unwrap_or_else(|| {
        format!(
            "info,\
            {}=debug",
            env!("CARGO_CRATE_NAME")
        )
    });

    let main_file = File::create(log_dir.join("main.log"))?;
    let main_layer = tracing_subscriber::fmt::layer()
        .with_writer(Mutex::new(main_file))
        .with_ansi(false)
        .with_filter(EnvFilter::new(main_filter));

    // Layer 2: API log - only "api_call" span (trace level)
    let api_file = File::create(log_dir.join("api.log"))?;
    let api_layer = tracing_subscriber::fmt::layer()
        .with_writer(Mutex::new(api_file))
        .with_ansi(false)
        .with_filter(
            EnvFilter::new("trace")
                .add_directive("api_call=trace".parse().unwrap()),
        );

    // Layer 3: Search log - only "search" span (debug level)
    let search_file = File::create(log_dir.join("search.log"))?;
    let search_layer = tracing_subscriber::fmt::layer()
        .with_writer(Mutex::new(search_file))
        .with_ansi(false)
        .with_filter(
            EnvFilter::new("debug")
                .add_directive("search=debug".parse().unwrap()),
        );

    // Layer 4: Error log - only errors from anywhere
    let error_file = File::create(log_dir.join("error.log"))?;
    let error_layer = tracing_subscriber::fmt::layer()
        .with_writer(Mutex::new(error_file))
        .with_ansi(false)
        .with_filter(EnvFilter::new("error"));

    tracing_subscriber::registry()
        .with(main_layer)
        .with(api_layer)
        .with(search_layer)
        .with(error_layer)
        .with(ErrorLayer::default())
        .init();

    Ok(log_dir)
}

// =============================================================================
// RESOURCES - Stored in ECS World
// =============================================================================

#[derive(Resource)]
struct ApiConfig {
    base_url: String,
    api_key: String,
}

#[derive(Resource, Debug, Clone)]
struct SearchState {
    query: Option<SearchQuery>,
    loading: bool,
    results: Vec<SearchResult>,
    error: Option<String>,
    search_terms: Vec<String>,
    current_term_index: usize,
}

#[derive(Debug, Clone)]
struct SearchResult {
    title: String,
    artist: String,
    play_count: i32,
}

impl Default for SearchState {
    fn default() -> Self {
        Self {
            query: None,
            loading: false,
            results: vec![],
            error: None,
            search_terms: vec![
                "".to_string(),           // Empty search - gets all recent
                "Untitled".to_string(),   // Known to have 712 results
                "Bounce".to_string(),     // Known to have 2 results
                "rock".to_string(),
                "jazz".to_string(),
                "electronic".to_string(),
                "pop".to_string(),
            ],
            current_term_index: 0,
        }
    }
}

// =============================================================================
// COMPONENTS - Attached to entities
// =============================================================================

#[derive(Component)]
struct SearchTask {
    handle: Arc<Mutex<Option<Result<Vec<SearchResult>, String>>>>,
}

// =============================================================================
// MAIN - Manual ECS setup and event loop
// =============================================================================

fn main() -> Result<()> {
    let startup = Instant::now();

    // Initialize file-based logging
    let log_dir = initialize_logging()?;
    info!("=== Loading Example Starting ===");
    info!("Application logs will be written to file");

    // Load environment variables
    let _ = dotenvy::dotenv();

    let base_url = std::env::var("SUNO_BASE_URL")
        .unwrap_or_else(|_| "http://127.0.0.1:8000".to_string());
    let api_key = std::env::var("SUNO_API_KEY")
        .unwrap_or_else(|_| "demo-key".to_string());

    info!("API Config - base_url: {}, api_key: {}", base_url, if api_key == "demo-key" { "demo-key" } else { "***" });

    // Create ECS World - this is fast (~1ms)
    let mut world = World::new();

    // Insert resources
    world.insert_resource(ApiConfig { base_url, api_key });
    world.insert_resource(SearchState::default());

    info!("ECS initialized in {:?}", startup.elapsed());

    // Initialize terminal
    enable_raw_mode()?;
    stdout().execute(EnterAlternateScreen)?;
    let mut terminal = Terminal::new(ratatui::backend::CrosstermBackend::new(stdout()))?;

    info!("Terminal initialized in {:?}", startup.elapsed());
    info!("Total startup time: {:?}", startup.elapsed());

    // Main loop
    let mut should_quit = false;
    let frame_duration = Duration::from_millis(16); // 60 FPS

    while !should_quit {
        let frame_start = Instant::now();

        // 1. Handle input
        if event::poll(Duration::from_millis(0))? {
            if let Event::Key(key) = event::read()? {
                if key.kind == KeyEventKind::Press {
                    match key.code {
                        KeyCode::Char(' ') => {
                            info!("Space key pressed - triggering search");
                            trigger_search(&mut world);
                        }
                        KeyCode::Char('q') | KeyCode::Esc => {
                            info!("Quit key pressed");
                            should_quit = true;
                        }
                        _ => {}
                    }
                }
            }
        }

        // 2. Poll async tasks
        poll_search_tasks(&mut world);

        // 3. Render UI
        let search_state = world.resource::<SearchState>().clone();
        terminal.draw(|frame| {
            render_ui(frame, &search_state);
        })?;

        // 4. Frame timing
        let elapsed = frame_start.elapsed();
        if elapsed < frame_duration {
            std::thread::sleep(frame_duration - elapsed);
        }
    }

    // Cleanup
    disable_raw_mode()?;
    stdout().execute(LeaveAlternateScreen)?;

    // Print log info
    println!("\n📁 Logs directory: {}", log_dir.display());
    println!("  main.log   - All application logs");
    println!("  api.log    - API call details (trace level)");
    println!("  search.log - Search operations");
    println!("  error.log  - Errors only");

    Ok(())
}

// =============================================================================
// GAME LOGIC FUNCTIONS - Manual system implementations
// =============================================================================

#[instrument(skip_all)]
fn trigger_search(world: &mut World) {
    // Create "search" span for all search-related logs
    let span = span!(Level::DEBUG, "search");
    let _guard = span.enter();

    info!("Starting new search task");

    // Cancel any existing search tasks
    let mut existing_tasks: Vec<Entity> = Vec::new();
    {
        let query = world.query::<Entity>().iter(world).collect::<Vec<_>>();
        for entity in query {
            if world.entity(entity).contains::<SearchTask>() {
                existing_tasks.push(entity);
            }
        }
    }
    for entity in existing_tasks {
        info!("Canceling existing task");
        world.despawn(entity);
    }

    // Get mutable reference to search state
    let mut search_state = world.resource_mut::<SearchState>();

    // Cycle to next search term
    search_state.current_term_index =
        (search_state.current_term_index + 1) % search_state.search_terms.len();

    let current_term = search_state.search_terms[search_state.current_term_index].clone();
    let search_term = if current_term.is_empty() {
        None
    } else {
        Some(current_term)
    };

    info!("Search term: {:?}", search_term);

    search_state.loading = true;
    search_state.results.clear();
    search_state.error = None;

    // Create a search query using the new ergonomic builder API
    let search_term_for_query = search_term.clone().unwrap_or_else(|| "music".to_string());

    let query = match SearchQuery::public_song(search_term_for_query)
        .rank_by("most_relevant")
        .size(10)
        .build()
    {
        Ok(q) => q,
        Err(e) => {
            let err_msg = format!("Failed to build search query: {}", e);
            error!("{}", err_msg);
            search_state.error = Some(err_msg);
            search_state.loading = false;
            return;
        }
    };

    search_state.query = Some(query.clone());

    // Get API config
    let api_config = world.resource::<ApiConfig>();
    let base_url = api_config.base_url.clone();
    let api_key = api_config.api_key.clone();

    // Spawn async task in background thread
    let result_handle = Arc::new(Mutex::new(None));
    let result_handle_clone = result_handle.clone();

    thread::spawn(move || {
        let result = perform_search_request(base_url, api_key, query);
        *result_handle_clone.lock().unwrap() = Some(result);
    });

    // Spawn an entity with the task component
    info!("Task spawned, creating entity");
    world.spawn(SearchTask { handle: result_handle });
}

fn perform_search_request(
    base_url: String,
    api_key: String,
    query: SearchQuery,
) -> Result<Vec<SearchResult>, String> {
    // Create "api_call" span for detailed API logging
    let span = span!(Level::DEBUG, "api_call");
    let _guard = span.enter();

    debug!("Starting search request to: {}/api/search", base_url);
    debug!("Search term: {:?}", query.term);

    let client = reqwest::blocking::Client::new();

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

    trace!("Request body: {:?}", search_req);
    debug!("Sending HTTP POST request...");

    let response = client
        .post(format!("{}/api/search", base_url))
        .header("Content-Type", "application/json")
        .header("Authorization", format!("Bearer {}", api_key))
        .json(&search_req)
        .send()
        .map_err(|e| {
            let err = format!("Request failed: {}", e);
            error!("{}", err);
            err
        })?;

    let status = response.status();
    debug!("Response status: {}", status);

    if !status.is_success() {
        let err = format!("API returned error status: {}", status);
        error!("{}", err);
        return Err(err);
    }

    let body_text = response
        .text()
        .map_err(|e| {
            let err = format!("Failed to read response body: {}", e);
            error!("{}", err);
            err
        })?;

    debug!("Response body length: {} bytes", body_text.len());
    trace!("Response body: {}", &body_text[..body_text.len().min(500)]);

    let search_resp: SearchResponse = serde_json::from_str(&body_text)
        .map_err(|e| {
            let err = format!("Failed to parse JSON response: {}", e);
            error!("{}", err);
            trace!("Invalid JSON body: {}", body_text);
            err
        })?;

    let mut results = Vec::new();
    debug!("Processing {} query results", search_resp.result.len());

    for (query_name, search_result) in search_resp.result.iter() {
        debug!(
            "Query '{}': {} total hits, {} items returned",
            query_name, search_result.total_hits, search_result.result.len()
        );

        // Use continue pattern for resilient processing
        for item in search_result.result.iter().take(10) {
            let SearchResultItem::Song(clip) = item else {
                trace!("Skipping non-song item");
                continue;
            };

            // Validate data before using
            if clip.title.is_empty() {
                warn!("Clip {} has empty title, skipping", clip.id);
                continue;
            }

            debug!("Found song: '{}' by '{}'", clip.title, clip.display_name);
            results.push(SearchResult {
                title: clip.title.clone(),
                artist: clip.display_name.clone(),
                play_count: clip.play_count,
            });
        }
    }

    info!("Search complete: {} results parsed", results.len());

    if results.is_empty() {
        Err("No results found".to_string())
    } else {
        Ok(results)
    }
}

#[instrument(skip_all)]
fn poll_search_tasks(world: &mut World) {
    let mut completed_tasks = Vec::new();

    // Find all search tasks
    {
        let mut query = world.query::<(Entity, &SearchTask)>();
        for (entity, task) in query.iter(world) {
            // Check if the task is complete
            let mut guard = task.handle.lock().unwrap();
            if let Some(result) = guard.take() {
                completed_tasks.push((entity, result));
            }
        }
    }

    // Process completed tasks
    for (entity, result) in completed_tasks {
        let span = span!(Level::DEBUG, "search");
        let _guard = span.enter();

        info!("Background task completed");

        let mut search_state = world.resource_mut::<SearchState>();

        match result {
            Ok(results) => {
                info!("Success: {} results received from API", results.len());
                search_state.results = results;
                search_state.loading = false;
            }
            Err(error) => {
                error!("Search failed: {}", error);
                search_state.error = Some(error);
                search_state.loading = false;
            }
        }

        // Remove the completed task entity
        world.despawn(entity);
    }
}

fn render_ui(frame: &mut ratatui::Frame, search_state: &SearchState) {
    let area = frame.area();

    // Split layout: header, search area, results area
    let chunks = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Length(3),
            Constraint::Length(6),
            Constraint::Min(5),
        ])
        .split(area);

    // Header
    let header = Paragraph::new(Line::from(" Song Search - Real API ".bold().white().on_blue()))
        .alignment(Alignment::Center);
    frame.render_widget(header, chunks[0]);

    // Search status area
    let search_text = if let Some(error) = &search_state.error {
        format!("❌ Error: {}\n\nPress SPACE to try next search term", error)
    } else if let Some(query) = &search_state.query {
        let term_display = query.term.as_ref()
            .map(|t| t.as_str())
            .unwrap_or("<all recent>");

        if search_state.loading {
            format!(
                "🔍 Searching for: {}\n\nLoading...",
                term_display,
            )
        } else {
            format!(
                "✓ Search Complete\nQuery: {}\nResults: {}",
                term_display,
                search_state.results.len()
            )
        }
    } else {
        "Press SPACE to cycle through search terms:\n  • <all recent>\n  • Untitled (712 results)\n  • Bounce (2 results)\n  • rock\n  • jazz\n  • electronic\n  • pop".to_string()
    };

    let search_block = Block::bordered().title(" Search Status ");
    let search_para = Paragraph::new(search_text).block(search_block);
    frame.render_widget(search_para, chunks[1]);

    // Results area
    if search_state.results.is_empty() {
        let empty_msg = Paragraph::new("No results yet. Press SPACE to search.")
            .alignment(Alignment::Center);
        frame.render_widget(empty_msg, chunks[2]);
    } else {
        let results_block = Block::bordered()
            .title(format!(" Results ({}) ", search_state.results.len()))
            .border_set(ratatui::symbols::border::THICK);

        let header_cells = ["Title", "Artist", "Plays"]
            .iter()
            .map(|h| Cell::from(*h).bold().on_blue());
        let header = Row::new(header_cells).height(1);

        let rows = search_state.results.iter().map(|result| {
            let cells = vec![
                Cell::from(result.title.as_str()),
                Cell::from(result.artist.as_str()),
                Cell::from(result.play_count.to_string()),
            ];
            Row::new(cells).height(1)
        });

        let table = Table::new(
            rows,
            [
                Constraint::Percentage(50),
                Constraint::Percentage(35),
                Constraint::Percentage(15),
            ],
        )
        .header(header)
        .block(results_block);

        frame.render_widget(table, chunks[2]);
    }

    // Footer
    let footer = Paragraph::new(
        Line::from(" SPACE: Cycle Search Terms | Q/Esc: Quit ")
            .centered()
            .gray()
            .italic(),
    );
    let footer_area = ratatui::layout::Rect {
        x: area.x,
        y: area.bottom().saturating_sub(1),
        width: area.width,
        height: 1,
    };
    frame.render_widget(footer, footer_area);
}
