//! Download manager for offline media support
//!
//! This module handles downloading media from Jellyfin servers with:
//! - Priority-based queue management
//! - Progress tracking and event emission
//! - Retry logic with exponential backoff
//! - Resume support via HTTP Range requests

pub mod cache;
pub mod events;
pub mod network;
pub mod worker;

use crate::utils::lock::MutexSafe;
use std::collections::HashSet;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};

pub use worker::DownloadWorker;

/// Download manager coordinating downloads across workers
pub struct DownloadManager {
    /// Maximum concurrent downloads
    max_concurrent: usize,
    /// Currently active download IDs
    active_downloads: Arc<Mutex<HashSet<i64>>>,
}

impl DownloadManager {
    /// Create a new download manager
    pub fn new(_media_dir: PathBuf) -> Self {
        Self {
            max_concurrent: 3,
            active_downloads: Arc::new(Mutex::new(HashSet::new())),
        }
    }

    /// Get the maximum concurrent downloads
    pub fn max_concurrent(&self) -> usize {
        self.max_concurrent
    }

    /// Set the maximum concurrent downloads
    pub fn set_max_concurrent(&mut self, max: usize) {
        self.max_concurrent = max.max(1); // At least 1
    }

    /// Check if a new download can be started based on concurrent limit
    pub fn can_start_download(&self) -> bool {
        let active = self.active_downloads.lock_safe();
        active.len() < self.max_concurrent
    }

    /// Get the number of currently active downloads
    pub fn active_count(&self) -> usize {
        self.active_downloads.lock_safe().len()
    }

    /// Register a download as active
    pub fn register_download(&self, download_id: i64) -> bool {
        let mut active = self.active_downloads.lock_safe();
        if active.len() >= self.max_concurrent {
            return false;
        }
        active.insert(download_id)
    }

    /// Unregister a download when it completes or fails
    pub fn unregister_download(&self, download_id: i64) {
        let mut active = self.active_downloads.lock_safe();
        active.remove(&download_id);
    }

    /// Get a clone of the active downloads set (for internal use)
    pub fn get_active_downloads(&self) -> Arc<Mutex<HashSet<i64>>> {
        self.active_downloads.clone()
    }
}

/// Information about a download
#[derive(specta::Type, Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DownloadInfo {
    pub id: i64,
    pub item_id: String,
    pub user_id: String,
    pub file_path: String,
    pub file_size: Option<i64>,
    pub mime_type: Option<String>,
    pub status: String,
    pub progress: f64,
    pub bytes_downloaded: i64,
    pub queued_at: String,
    pub started_at: Option<String>,
    pub completed_at: Option<String>,
    pub error_message: Option<String>,
    pub retry_count: i32,
    pub priority: i32,
    // Item metadata for display (audio)
    pub item_name: Option<String>,
    pub artist_name: Option<String>,
    pub album_name: Option<String>,
    // Video-specific metadata
    pub series_name: Option<String>,
    pub season_name: Option<String>,
    pub episode_number: Option<i32>,
    pub season_number: Option<i32>,
    pub quality_preset: Option<String>,
    pub media_type: String,
    // Download source tracking
    pub download_source: String, // 'user' or 'auto'
}

/// Download task for workers
#[derive(Debug, Clone)]
pub struct DownloadTask {
    pub url: String,
    pub target_path: PathBuf,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_download_manager_set_max_concurrent() {
        let media_dir = PathBuf::from("/tmp/jellytau/media");
        let mut manager = DownloadManager::new(media_dir);

        manager.set_max_concurrent(5);
        assert_eq!(manager.max_concurrent(), 5);

        // Should clamp to minimum of 1
        manager.set_max_concurrent(0);
        assert_eq!(manager.max_concurrent(), 1);
    }

    #[test]
    fn test_download_info_serialization() {
        let info = DownloadInfo {
            id: 1,
            item_id: "test123".to_string(),
            user_id: "user1".to_string(),
            file_path: "/path/to/file.mp3".to_string(),
            file_size: Some(1024000),
            mime_type: Some("audio/mpeg".to_string()),
            status: "downloading".to_string(),
            progress: 0.5,
            bytes_downloaded: 512000,
            queued_at: "2024-01-01T00:00:00Z".to_string(),
            started_at: Some("2024-01-01T00:01:00Z".to_string()),
            completed_at: None,
            error_message: None,
            retry_count: 0,
            priority: 0,
            item_name: Some("Test Song".to_string()),
            artist_name: Some("Test Artist".to_string()),
            album_name: Some("Test Album".to_string()),
            series_name: None,
            season_name: None,
            episode_number: None,
            season_number: None,
            quality_preset: Some("original".to_string()),
            media_type: "audio".to_string(),
            download_source: "user".to_string(),
        };

        let json = serde_json::to_string(&info).unwrap();
        assert!(json.contains("\"status\":\"downloading\""));
        assert!(json.contains("\"progress\":0.5"));
        assert!(json.contains("\"itemName\":\"Test Song\""));
        assert!(json.contains("\"mediaType\":\"audio\""));
    }

    #[test]
    fn test_video_download_info_serialization() {
        let info = DownloadInfo {
            id: 2,
            item_id: "episode123".to_string(),
            user_id: "user1".to_string(),
            file_path: "/path/to/ShowName/S01E01_Title.mp4".to_string(),
            file_size: Some(1024000000),
            mime_type: Some("video/mp4".to_string()),
            status: "completed".to_string(),
            progress: 1.0,
            bytes_downloaded: 1024000000,
            queued_at: "2024-01-01T00:00:00Z".to_string(),
            started_at: Some("2024-01-01T00:01:00Z".to_string()),
            completed_at: Some("2024-01-01T01:00:00Z".to_string()),
            error_message: None,
            retry_count: 0,
            priority: 100,
            item_name: Some("Episode Title".to_string()),
            artist_name: None,
            album_name: None,
            series_name: Some("Show Name".to_string()),
            season_name: Some("Season 1".to_string()),
            episode_number: Some(1),
            season_number: Some(1),
            quality_preset: Some("high".to_string()),
            media_type: "video".to_string(),
            download_source: "auto".to_string(),
        };

        let json = serde_json::to_string(&info).unwrap();
        assert!(json.contains("\"mediaType\":\"video\""));
        assert!(json.contains("\"seriesName\":\"Show Name\""));
        assert!(json.contains("\"episodeNumber\":1"));
        assert!(json.contains("\"qualityPreset\":\"high\""));
    }
}
