//! Main processor for Topaz Video AI operations

use std::path::{Path, PathBuf};
use std::time::Duration;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::core::{TvaiError, FfmpegManager};
use crate::utils::temp::TempFileManager;

/// Progress callback function type
pub type ProgressCallback = Box<dyn Fn(f32) + Send + Sync>;

/// Video information extracted from analysis
#[derive(Debug, Clone)]
pub struct VideoInfo {
    /// Detected frames per second
    pub fps: Option<f64>,
    /// Time base rate
    pub tbr: Option<f64>,
    /// Whether there's a framerate inconsistency
    pub has_framerate_inconsistency: bool,
    /// Target framerate to use for processing
    pub target_framerate: f64,
}

/// Processing options with progress callback
#[derive(Default)]
pub struct ProcessingOptions {
    /// Optional progress callback
    pub progress_callback: Option<ProgressCallback>,
    /// Whether to save intermediate files for debugging
    pub save_intermediate: bool,
    /// Custom operation ID (auto-generated if None)
    pub operation_id: Option<String>,
}

/// Main processor for Topaz Video AI operations
#[derive(Debug)]
pub struct TvaiProcessor {
    config: TvaiConfig,
    ffmpeg_manager: FfmpegManager,
    temp_manager: TempFileManager,
}

/// Configuration for TvaiProcessor
#[derive(Debug, Clone)]
pub struct TvaiConfig {
    /// Path to Topaz Video AI installation
    pub topaz_path: PathBuf,
    /// Whether to use GPU acceleration
    pub use_gpu: bool,
    /// Custom temporary directory (optional)
    pub temp_dir: Option<PathBuf>,
    /// Force use of Topaz FFmpeg even for non-AI operations
    pub force_topaz_ffmpeg: bool,
}

/// Result of a processing operation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessResult {
    /// Path to the output file
    pub output_path: PathBuf,
    /// Time taken for processing
    pub processing_time: Duration,
    /// Additional metadata about the processing
    pub metadata: ProcessMetadata,
}

/// Metadata about a processing operation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessMetadata {
    /// Unique operation ID
    pub operation_id: String,
    /// Input file path
    pub input_path: PathBuf,
    /// Processing parameters used
    pub parameters: String,
    /// Whether GPU was used
    pub used_gpu: bool,
    /// FFmpeg version used
    pub ffmpeg_version: Option<String>,
}

impl TvaiConfig {
    /// Create a new configuration builder
    pub fn builder() -> TvaiConfigBuilder {
        TvaiConfigBuilder::default()
    }
    
    /// Create a configuration with default settings
    pub fn new(topaz_path: PathBuf) -> Self {
        Self {
            topaz_path,
            use_gpu: true,
            temp_dir: None,
            force_topaz_ffmpeg: true,
        }
    }
}

/// Builder for TvaiConfig
#[derive(Debug, Default)]
pub struct TvaiConfigBuilder {
    topaz_path: Option<PathBuf>,
    use_gpu: Option<bool>,
    temp_dir: Option<PathBuf>,
    force_topaz_ffmpeg: Option<bool>,
}

impl TvaiConfigBuilder {
    /// Set the Topaz Video AI installation path
    pub fn topaz_path<P: AsRef<Path>>(mut self, path: P) -> Self {
        self.topaz_path = Some(path.as_ref().to_path_buf());
        self
    }
    
    /// Set whether to use GPU acceleration
    pub fn use_gpu(mut self, use_gpu: bool) -> Self {
        self.use_gpu = Some(use_gpu);
        self
    }
    
    /// Set custom temporary directory
    pub fn temp_dir<P: AsRef<Path>>(mut self, path: P) -> Self {
        self.temp_dir = Some(path.as_ref().to_path_buf());
        self
    }
    
    /// Set whether to force Topaz FFmpeg usage
    pub fn force_topaz_ffmpeg(mut self, force: bool) -> Self {
        self.force_topaz_ffmpeg = Some(force);
        self
    }
    
    /// Build the configuration
    pub fn build(self) -> Result<TvaiConfig, TvaiError> {
        let topaz_path = self.topaz_path
            .ok_or_else(|| TvaiError::InvalidParameter("topaz_path is required".to_string()))?;
        
        if !topaz_path.exists() {
            return Err(TvaiError::TopazNotFound(topaz_path.display().to_string()));
        }
        
        Ok(TvaiConfig {
            topaz_path,
            use_gpu: self.use_gpu.unwrap_or(true),
            temp_dir: self.temp_dir,
            force_topaz_ffmpeg: self.force_topaz_ffmpeg.unwrap_or(true),
        })
    }
}

impl TvaiProcessor {
    /// Create a new processor with the given configuration
    pub fn new(config: TvaiConfig) -> Result<Self, TvaiError> {
        let ffmpeg_manager = FfmpegManager::new(
            Some(&config.topaz_path),
            config.force_topaz_ffmpeg,
        );

        // Verify Topaz FFmpeg is available for AI operations
        if !ffmpeg_manager.is_topaz_available() {
            return Err(TvaiError::TopazNotFound(
                config.topaz_path.display().to_string()
            ));
        }

        // Set up temporary file manager
        let temp_manager = TempFileManager::new(config.temp_dir.as_deref())?;

        Ok(Self {
            config,
            ffmpeg_manager,
            temp_manager,
        })
    }
    
    /// Get the configuration
    pub fn config(&self) -> &TvaiConfig {
        &self.config
    }
    
    /// Get the FFmpeg manager
    pub fn ffmpeg_manager(&self) -> &FfmpegManager {
        &self.ffmpeg_manager
    }
    
    /// Get the temporary directory
    pub fn temp_dir(&self) -> &Path {
        self.temp_manager.base_dir()
    }

    /// Get mutable access to the temporary file manager
    pub fn temp_manager_mut(&mut self) -> &mut TempFileManager {
        &mut self.temp_manager
    }

    /// Get read-only access to the temporary file manager
    pub fn temp_manager(&self) -> &TempFileManager {
        &self.temp_manager
    }
    
    /// Generate a unique operation ID
    pub fn generate_operation_id(&self) -> String {
        Uuid::new_v4().to_string()
    }
    
    /// Create a temporary file path with the given extension
    pub fn create_temp_path(&mut self, operation_id: &str, suffix: &str) -> PathBuf {
        self.temp_manager.create_temp_path(operation_id, suffix)
    }

    /// Create a unique temporary file path
    pub fn create_unique_temp_path(&mut self, suffix: &str) -> PathBuf {
        self.temp_manager.create_unique_temp_path(suffix)
    }

    /// Create a temporary directory for an operation
    pub fn create_temp_dir(&mut self, operation_id: &str) -> Result<PathBuf, TvaiError> {
        self.temp_manager.create_temp_dir(operation_id)
    }

    /// Clean up temporary files for an operation
    pub fn cleanup_temp_files(&mut self, operation_id: &str) -> Result<(), TvaiError> {
        self.temp_manager.cleanup_operation(operation_id)
    }

    /// Clean up all temporary files
    pub fn cleanup_all_temp_files(&mut self) -> Result<(), TvaiError> {
        self.temp_manager.cleanup_all()
    }
    
    /// Check if GPU is available and enabled
    pub fn is_gpu_enabled(&self) -> bool {
        self.config.use_gpu && self.is_gpu_available()
    }
    
    /// Check if GPU is available on the system
    pub fn is_gpu_available(&self) -> bool {
        // This is a simplified check - in a real implementation,
        // you might want to check for CUDA/OpenCL availability
        self.config.use_gpu
    }

    /// Execute FFmpeg command with progress tracking
    pub async fn execute_ffmpeg_command(
        &self,
        args: &[&str],
        for_topaz: bool,
        progress_callback: Option<&ProgressCallback>,
    ) -> Result<std::process::Output, TvaiError> {
        // Report start progress
        if let Some(callback) = progress_callback {
            callback(0.0);
        }

        let output = self.ffmpeg_manager.execute_command(args, for_topaz).await?;

        // Report completion
        if let Some(callback) = progress_callback {
            callback(1.0);
        }

        Ok(output)
    }

    /// Validate input file exists and is readable
    pub fn validate_input_file(&self, path: &Path) -> Result<(), TvaiError> {
        if !path.exists() {
            return Err(TvaiError::FileNotFound(path.display().to_string()));
        }

        if !path.is_file() {
            return Err(TvaiError::InvalidParameter(
                format!("Path is not a file: {}", path.display())
            ));
        }

        // Try to read the file to check permissions
        std::fs::File::open(path)
            .map_err(|e| TvaiError::IoError(e))?;

        Ok(())
    }

    /// Validate output directory is writable
    pub fn validate_output_path(&self, path: &Path) -> Result<(), TvaiError> {
        if let Some(parent) = path.parent() {
            if !parent.exists() {
                std::fs::create_dir_all(parent)?;
            }

            // Check if directory is writable by creating a temp file
            let test_file = parent.join(".tvai_write_test");
            std::fs::write(&test_file, b"test")
                .map_err(|e| TvaiError::IoError(e))?;
            std::fs::remove_file(&test_file)?;
        }

        Ok(())
    }

    /// Get FFmpeg version information
    pub async fn get_ffmpeg_version(&self, for_topaz: bool) -> Result<String, TvaiError> {
        self.ffmpeg_manager.get_version(for_topaz).await
    }

    /// Analyze input video to detect framerate issues
    pub async fn analyze_input_video(&self, input_path: &Path) -> Result<VideoInfo, TvaiError> {
        let input_path_str = input_path.to_string_lossy().to_string();
        let args = vec![
            "-hide_banner",
            "-i", &input_path_str,
            "-f", "null",
            "-",
        ];

        let output = self.ffmpeg_manager.execute_command(&args, false).await?;
        let stderr = String::from_utf8_lossy(&output.stderr);

        // Parse video information from stderr
        let mut fps = None;
        let mut tbr = None;
        let mut has_inconsistency = false;

        for line in stderr.lines() {
            if line.contains("Stream #") && line.contains("Video:") {
                // Extract fps and tbr values
                if let Some(fps_match) = extract_fps_from_line(line) {
                    fps = Some(fps_match);
                }
                if let Some(tbr_match) = extract_tbr_from_line(line) {
                    tbr = Some(tbr_match);
                }
            }
        }

        // Check for framerate inconsistency
        if let (Some(fps_val), Some(tbr_val)) = (fps, tbr) {
            // If fps and tbr differ significantly, we have an inconsistency
            let diff = (fps_val - tbr_val).abs();
            has_inconsistency = diff > 1.0; // More than 1 fps difference
        }

        // Determine target framerate
        let target_framerate = match fps {
            Some(f) if f > 0.0 => {
                // Round to nearest standard framerate
                if f <= 24.5 { 24.0 }
                else if f <= 25.5 { 25.0 }
                else if f <= 30.5 { 30.0 }
                else { 60.0 }
            }
            _ => 24.0, // Default fallback
        };

        Ok(VideoInfo {
            fps,
            tbr,
            has_framerate_inconsistency: has_inconsistency,
            target_framerate,
        })
    }

    /// Process video with automatic framerate handling
    pub async fn process_video_with_filters(
        &self,
        input_path: &Path,
        output_path: &Path,
        filter_string: &str,
        for_topaz: bool,
        progress_callback: Option<&ProgressCallback>,
    ) -> Result<(), TvaiError> {
        // Validate input
        self.validate_input_file(input_path)?;
        self.validate_output_path(output_path)?;

        // Analyze input video
        let video_info = self.analyze_input_video(input_path).await?;

        // Build FFmpeg command
        let mut args = vec!["-hide_banner".to_string()];

        // Add framerate normalization if needed
        if video_info.has_framerate_inconsistency {
            args.extend([
                "-r".to_string(),
                video_info.target_framerate.to_string()
            ]);
        }

        // Input file
        args.extend(["-i".to_string(), input_path.to_string_lossy().to_string()]);

        // Add filter
        if !filter_string.is_empty() {
            args.extend(["-filter_complex".to_string(), filter_string.to_string()]);
        }

        // Output settings
        args.extend([
            "-c:v".to_string(), "h264_nvenc".to_string(),
            "-preset".to_string(), "p4".to_string(),
            "-crf".to_string(), "18".to_string(),
            "-pix_fmt".to_string(), "yuv420p".to_string(),
        ]);

        // Force output framerate if needed
        if video_info.has_framerate_inconsistency {
            args.extend(["-r".to_string(), video_info.target_framerate.to_string()]);
        }

        // Force overwrite and output file
        args.extend(["-y".to_string(), output_path.to_string_lossy().to_string()]);

        // Convert to string references
        let args_refs: Vec<&str> = args.iter().map(|s| s.as_str()).collect();

        // Execute command
        if let Some(callback) = progress_callback {
            callback(0.0);
        }

        let output = self.ffmpeg_manager.execute_command(&args_refs, for_topaz).await?;

        if let Some(callback) = progress_callback {
            callback(1.0);
        }

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(TvaiError::ProcessingError(
                format!("FFmpeg processing failed: {}", stderr)
            ));
        }

        Ok(())
    }

    /// Create processing metadata
    pub fn create_metadata(
        &self,
        operation_id: String,
        input_path: &Path,
        parameters: String,
    ) -> ProcessMetadata {
        ProcessMetadata {
            operation_id,
            input_path: input_path.to_path_buf(),
            parameters,
            used_gpu: self.is_gpu_enabled(),
            ffmpeg_version: None, // Will be populated during processing
        }
    }

    /// Verify if a TVAI model is available
    pub async fn verify_model(&self, model_name: &str, model_type: &str) -> Result<bool, TvaiError> {
        let filter = match model_type {
            "upscale" => format!("tvai_up=model={}:scale=2", model_name),
            "interpolation" => format!("tvai_fi=model={}:fps=24", model_name),
            _ => return Err(TvaiError::InvalidParameter("Unknown model type".to_string())),
        };

        let args = vec![
            "-hide_banner",
            "-f", "lavfi",
            "-i", "testsrc=duration=0.05:size=320x240:rate=1",
            "-vf", &filter,
            "-f", "null",
            "-",
        ];

        let output = self.ffmpeg_manager.execute_command(&args, true).await?;
        Ok(output.status.success())
    }

    /// Suggest alternative model if the requested one is not available
    pub async fn suggest_alternative_model(&self, requested_model: &str, model_type: &str) -> Result<Option<String>, TvaiError> {
        // First check if the requested model is available
        if self.verify_model(requested_model, model_type).await.unwrap_or(false) {
            return Ok(Some(requested_model.to_string()));
        }

        // Suggest alternatives based on use case
        let alternatives = match model_type {
            "upscale" => {
                match requested_model {
                    "nyx-3" => vec!["iris-3", "iris-2", "ahq-12"], // Portrait alternatives
                    "iris-3" => vec!["iris-2", "ahq-12", "nyx-3"], // General alternatives
                    "iris-2" => vec!["iris-3", "ahq-12", "nyx-3"], // General alternatives
                    _ => vec!["iris-3", "iris-2", "ahq-12"], // Default alternatives
                }
            }
            "interpolation" => {
                vec!["chr-2", "apf-2", "apf-1"] // Default interpolation alternatives
            }
            _ => return Err(TvaiError::InvalidParameter("Unknown model type".to_string())),
        };

        // Test alternatives in order of preference
        for alt in alternatives {
            if self.verify_model(alt, model_type).await.unwrap_or(false) {
                return Ok(Some(alt.to_string()));
            }
        }

        Ok(None)
    }
}

impl Drop for TvaiProcessor {
    fn drop(&mut self) {
        // TempFileManager will handle cleanup automatically
        // when it's dropped due to its auto_cleanup feature
    }
}

/// Extract fps value from FFmpeg output line
fn extract_fps_from_line(line: &str) -> Option<f64> {
    // Look for pattern like "24.12 fps"
    if let Some(fps_pos) = line.find(" fps") {
        let before_fps = &line[..fps_pos];
        if let Some(space_pos) = before_fps.rfind(' ') {
            let fps_str = &before_fps[space_pos + 1..];
            return fps_str.parse::<f64>().ok();
        }
    }
    None
}

/// Extract tbr value from FFmpeg output line
fn extract_tbr_from_line(line: &str) -> Option<f64> {
    // Look for pattern like "60 tbr"
    if let Some(tbr_pos) = line.find(" tbr") {
        let before_tbr = &line[..tbr_pos];
        if let Some(space_pos) = before_tbr.rfind(' ') {
            let tbr_str = &before_tbr[space_pos + 1..];
            return tbr_str.parse::<f64>().ok();
        }
    }
    None
}

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

    fn create_test_config() -> TvaiConfig {
        // Use a fake path for testing
        TvaiConfig::new(PathBuf::from("/fake/topaz/path"))
    }

    #[test]
    fn test_config_builder() {
        let config = TvaiConfig::builder()
            .topaz_path("/test/path")
            .use_gpu(false)
            .force_topaz_ffmpeg(false);

        // This will fail because the path doesn't exist, but that's expected in tests
        assert!(config.build().is_err());
    }

    #[test]
    fn test_operation_id_generation() {
        let id1 = uuid::Uuid::new_v4().to_string();
        let id2 = uuid::Uuid::new_v4().to_string();

        assert_ne!(id1, id2);
        assert_eq!(id1.len(), 36); // Standard UUID length
    }

    #[test]
    fn test_processing_options_default() {
        let options = ProcessingOptions::default();
        assert!(options.progress_callback.is_none());
        assert!(!options.save_intermediate);
        assert!(options.operation_id.is_none());
    }
}
