//! Recording module for video capture using libobs.
//!
//! This module provides the recording engine that captures game footage
//! and encodes it using hardware or software encoders.

mod capture;
pub mod encoder;
mod obs_context;
mod output;

pub use capture::{CaptureMode, GameCapture, MonitorCapture, SourceType, WindowCapture};
pub use encoder::{AudioEncoderConfig, EncoderCapability, EncoderConfig, EncoderSettings};
pub use obs_context::{ObsContext, ObsContextBuilder};
pub use output::{OutputConfig, OutputFormat, RecordingOutput, RecordingResult, RecordingStats};

use std::sync::Arc;

use chrono::{DateTime, Utc};
use parking_lot::RwLock;
use tracing::{error, info, warn};

use crate::config::Settings;
use crate::error::{RecordingError, Result};

/// Recording engine that manages the entire recording pipeline.
pub struct RecordingEngine {
    /// OBS context.
    context: Option<ObsContext>,
    /// Current settings.
    settings: Arc<RwLock<Settings>>,
    /// Current recording output.
    current_output: Option<RecordingOutput>,
    /// Recording start time.
    start_time: Option<DateTime<Utc>>,
    /// Whether recording is active.
    is_recording: bool,
}

impl RecordingEngine {
    /// Create a new recording engine.
    pub fn new(settings: Settings) -> Self {
        Self {
            context: None,
            settings: Arc::new(RwLock::new(settings)),
            current_output: None,
            start_time: None,
            is_recording: false,
        }
    }

    /// Initialize the recording engine.
    ///
    /// This sets up OBS and prepares for recording.
    pub fn initialize(&mut self) -> Result<()> {
        info!("Initializing recording engine");

        let settings = self.settings.read().clone();

        // Build OBS context
        let context = ObsContextBuilder::new()
            .with_video_settings(settings.video.clone())
            .with_audio_settings(settings.audio.clone())
            .with_output_dir(&settings.output.path)
            .build()?;

        self.context = Some(context);
        info!("Recording engine initialized successfully");

        Ok(())
    }

    /// Update settings.
    pub fn update_settings(&self, new_settings: Settings) -> Result<()> {
        let mut settings = self.settings.write();
        *settings = new_settings;
        Ok(())
    }

    /// Get current settings.
    pub fn settings(&self) -> Settings {
        self.settings.read().clone()
    }

    /// Check if currently recording.
    pub fn is_recording(&self) -> bool {
        self.is_recording
    }

    /// Get the current game ID if recording.
    pub fn current_game_id(&self) -> Option<u64> {
        self.current_output.as_ref().and_then(|o| o.game_id)
    }

    /// Start recording.
    ///
    /// # Arguments
    /// * `game_id` - Optional game ID for the recording.
    /// * `champion` - Optional champion name for the filename.
    pub fn start_recording(&mut self, game_id: Option<u64>, champion: Option<&str>) -> Result<()> {
        info!(
            "RecordingEngine::start_recording called - game_id: {:?}, champion: {:?}",
            game_id, champion
        );

        if self.is_recording {
            warn!("Already recording, returning error");
            return Err(RecordingError::AlreadyRecording.into());
        }

        info!("Reading settings...");
        let settings = self.settings.read().clone();

        // Generate output filename
        let filename = self.generate_filename(game_id, champion);
        let output_path = settings.output.path.join(&filename);

        info!("Starting recording to: {:?}", output_path);

        // Start the recording
        let context = self.context.as_mut().ok_or_else(|| {
            error!("OBS not initialized when start_recording was called");
            RecordingError::ObsInitError("OBS not initialized".to_string())
        })?;

        info!("Calling OBS context start_recording...");
        context.start_recording(&output_path)?;
        info!("OBS context start_recording returned successfully");

        self.current_output = Some(RecordingOutput {
            path: output_path,
            game_id,
            champion: champion.map(String::from),
        });
        self.start_time = Some(Utc::now());
        self.is_recording = true;

        info!("Recording started successfully");
        Ok(())
    }

    /// Stop recording.
    pub fn stop_recording(&mut self) -> Result<RecordingResult> {
        if !self.is_recording {
            return Err(RecordingError::NotRecording.into());
        }

        let context = self.context.as_mut().ok_or(RecordingError::ObsInitError(
            "OBS not initialized".to_string(),
        ))?;

        info!("Stopping recording");

        // Stop the recording
        context.stop_recording()?;

        let output = self
            .current_output
            .take()
            .ok_or(RecordingError::StopError("No active output".to_string()))?;

        let start_time = self
            .start_time
            .take()
            .ok_or(RecordingError::StopError("No start time".to_string()))?;

        let end_time = Utc::now();
        let duration = end_time - start_time;

        self.is_recording = false;

        let result = RecordingResult {
            path: output.path,
            game_id: output.game_id,
            champion: output.champion,
            start_time,
            end_time,
            duration,
            stats: None, // Stats would be populated from OBS context if available
        };

        info!("Recording stopped: {:?}", result.path);
        Ok(result)
    }

    /// Get the current recording timestamp (time since recording started).
    pub fn current_timestamp(&self) -> Option<chrono::Duration> {
        if !self.is_recording {
            return None;
        }

        self.start_time.map(|start| Utc::now() - start)
    }

    /// Generate a filename for the recording.
    fn generate_filename(&self, game_id: Option<u64>, champion: Option<&str>) -> String {
        let settings = self.settings.read();
        let now = Utc::now();

        let date = now.format("%Y-%m-%d").to_string();
        let time = now.format("%H-%M-%S").to_string();
        let game_id_str = game_id.map(|id| id.to_string()).unwrap_or_default();
        let champion_str = champion.unwrap_or("unknown");

        let filename = settings
            .output
            .naming_pattern
            .replace("{date}", &date)
            .replace("{time}", &time)
            .replace("{game_id}", &game_id_str)
            .replace("{champion}", champion_str);

        format!("{}.{}", filename, settings.output.container)
    }

    /// Shutdown the recording engine.
    pub fn shutdown(&mut self) -> Result<()> {
        if self.is_recording {
            self.stop_recording()?;
        }

        if let Some(mut context) = self.context.take() {
            context.shutdown()?;
        }

        info!("Recording engine shut down");
        Ok(())
    }
}

impl Drop for RecordingEngine {
    fn drop(&mut self) {
        if let Err(e) = self.shutdown() {
            warn!("Error shutting down recording engine: {}", e);
        }
    }
}

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

    #[test]
    fn test_recording_engine_creation() {
        let settings = Settings::default();
        let engine = RecordingEngine::new(settings);
        assert!(!engine.is_recording());
    }

    #[test]
    fn test_filename_generation() {
        let settings = Settings::default();
        let engine = RecordingEngine::new(settings);

        let filename = engine.generate_filename(Some(12345), Some("Ahri"));
        assert!(filename.ends_with(".mp4"));
        assert!(filename.contains("Ahri"));
    }
}
