//! OBS context initialization and management.
//!
//! This module handles the lifecycle of the OBS library context using libobs-simple.

use std::path::Path;

use libobs_simple::output::simple::{
    HardwareCodec, HardwarePreset, OutputFormat, SimpleOutputBuilder,
};
use libobs_simple::wrapper::data::output::ObsOutputTrait;
use libobs_simple::wrapper::data::video::ObsVideoInfoBuilder;
use libobs_simple::wrapper::data::ObsObjectBuilder;
use libobs_simple::wrapper::scenes::SceneItemExtSceneTrait;
use libobs_simple::wrapper::utils::{ObsPath, ObsString};
use libobs_wrapper::context::ObsContext as LibObsContext;
use libobs_wrapper::utils::StartupInfo;
use tracing::{error, info, warn};

use super::encoder::{best_available_encoder, EncoderCapability};
use crate::config::{AudioSettings, VideoSettings};
use crate::error::{RecordingError, Result};

/// Builder for OBS context.
pub struct ObsContextBuilder {
    video_settings: Option<VideoSettings>,
    audio_settings: Option<AudioSettings>,
    output_dir: Option<std::path::PathBuf>,
}

impl ObsContextBuilder {
    /// Create a new OBS context builder.
    pub fn new() -> Self {
        Self {
            video_settings: None,
            audio_settings: None,
            output_dir: None,
        }
    }

    /// Set video settings.
    pub fn with_video_settings(mut self, settings: VideoSettings) -> Self {
        self.video_settings = Some(settings);
        self
    }

    /// Set audio settings.
    pub fn with_audio_settings(mut self, settings: AudioSettings) -> Self {
        self.audio_settings = Some(settings);
        self
    }

    /// Set output directory.
    pub fn with_output_dir(mut self, dir: &Path) -> Self {
        self.output_dir = Some(dir.to_path_buf());
        self
    }

    /// Build the OBS context.
    pub fn build(self) -> Result<ObsContext> {
        let video_settings = self.video_settings.unwrap_or_default();
        let audio_settings = self.audio_settings.unwrap_or_default();
        let output_dir = self
            .output_dir
            .unwrap_or_else(|| std::path::PathBuf::from("./recordings"));

        // Ensure output directory exists
        if !output_dir.exists() {
            std::fs::create_dir_all(&output_dir)
                .map_err(|e| RecordingError::OutputDirError(e.to_string()))?;
        }

        let context = ObsContext {
            video_settings,
            audio_settings,
            output_dir,
            context: None,
            output: None,
            recording: false,
            current_output: None,
            encoder_capability: None,
        };

        Ok(context)
    }
}

impl Default for ObsContextBuilder {
    fn default() -> Self {
        Self::new()
    }
}

/// OBS context wrapper.
///
/// This manages the OBS library lifecycle and provides access to
/// recording functionality.
pub struct ObsContext {
    /// Video configuration.
    video_settings: VideoSettings,
    /// Audio configuration.
    audio_settings: AudioSettings,
    /// Output directory for recordings.
    output_dir: std::path::PathBuf,
    /// The underlying libobs context.
    context: Option<LibObsContext>,
    /// The current output.
    output: Option<libobs_simple::wrapper::data::output::ObsOutputRef>,
    /// Whether currently recording.
    recording: bool,
    /// Current output path (if recording).
    current_output: Option<std::path::PathBuf>,
    /// Detected encoder capability.
    encoder_capability: Option<EncoderCapability>,
}

impl ObsContext {
    /// Check if OBS is initialized.
    pub fn is_initialized(&self) -> bool {
        self.context.is_some()
    }

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

    /// Get the video settings.
    pub fn video_settings(&self) -> &VideoSettings {
        &self.video_settings
    }

    /// Get the output directory for recordings.
    pub fn output_dir(&self) -> &std::path::Path {
        &self.output_dir
    }

    /// Initialize OBS.
    fn initialize(&mut self) -> Result<()> {
        if self.context.is_some() {
            info!("OBS context already initialized");
            return Ok(());
        }

        info!("[OBS_INIT] Starting OBS context initialization...");

        // Pre-flight checks for OBS
        self.preflight_checks()?;

        // Detect best available encoder
        info!("[OBS_INIT] Detecting best available encoder...");

        let encoder = best_available_encoder();
        info!("[OBS_INIT] Detected encoder: {}", encoder.name());

        self.encoder_capability = Some(encoder);

        let (width, height) = self.video_settings.quality.resolution();
        let fps = self.video_settings.frame_rate;

        info!(
            "[OBS_INIT] OBS video config: {}x{} @ {}fps",
            width, height, fps
        );

        // Create startup info with video configuration
        info!("[OBS_INIT] Creating OBS video info builder...");

        let video_info = ObsVideoInfoBuilder::new()
            .fps_num(fps)
            .fps_den(1)
            .base_width(width)
            .base_height(height)
            .output_width(width)
            .output_height(height)
            .build();
        info!("[OBS_INIT] OBS video info built successfully");

        info!("[OBS_INIT] Building OBS context with LibObsContext::builder()...");

        let compat = LibObsContext::check_version_compatibility();
        if !compat {
            error!("OBS version compatibility is wrong. This will not go well...");
        }

        //FIXME: this is crashing here.
        // LibObsContext::new makes the whole application crash, even with default options
        let info = StartupInfo::default().set_video_info(video_info);
        let context_result =
            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| LibObsContext::new(info)));

        let context = match context_result {
            Ok(Ok(ctx)) => {
                info!("[OBS_INIT] OBS context created successfully");

                ctx
            }
            Ok(Err(e)) => {
                error!("[OBS_INIT] Failed to create OBS context: {:?}", e);

                return Err(RecordingError::ObsInitError(format!(
                    "Failed to create OBS context: {:?}",
                    e
                ))
                .into());
            }
            Err(panic_info) => {
                error!(
                    "[OBS_INIT] PANIC during OBS context creation: {:?}",
                    panic_info
                );

                eprintln!(
                    "[OBS_INIT] PANIC during OBS context creation: {:?}",
                    panic_info
                );
                return Err(RecordingError::ObsInitError(
                    "Panic during OBS context creation".to_string(),
                )
                .into());
            }
        };

        if self.audio_settings.enabled {
            info!(
                "[OBS_INIT] OBS audio config: {} channels @ {}Hz",
                self.audio_settings.channels, self.audio_settings.sample_rate
            );
        }

        self.context = Some(context);
        info!("[OBS_INIT] OBS context initialized successfully");

        Ok(())
    }

    /// Pre-flight checks for OBS initialization.
    fn preflight_checks(&self) -> Result<()> {
        info!("[PREFLIGHT] Running OBS pre-flight checks...");

        // Check for OBS installation directory
        // libobs-bootstrapper typically extracts OBS to a specific location
        let obs_paths = self.get_obs_search_paths();

        info!("[PREFLIGHT] OBS search paths: {:?}", obs_paths);

        let mut obs_found = false;
        for path in &obs_paths {
            if path.exists() {
                info!("[PREFLIGHT] Found OBS at: {:?}", path);

                // Check for plugins directory
                let plugins_path = path.join("obs-plugins");
                if plugins_path.exists() {
                    info!("[PREFLIGHT] Found OBS plugins at: {:?}", plugins_path);

                    // Check for 64-bit plugins
                    let plugins_64 = plugins_path.join("64bit");
                    if plugins_64.exists() {
                        info!("[PREFLIGHT] Found 64-bit plugins at: {:?}", plugins_64);

                        // List available plugins
                        if let Ok(entries) = std::fs::read_dir(&plugins_64) {
                            let plugins: Vec<String> = entries
                                .filter_map(|e| e.ok())
                                .filter_map(|e| e.file_name().to_str().map(|s| s.to_string()))
                                .collect();
                            info!("[PREFLIGHT] Available plugins: {:?}", plugins);
                        }
                    }
                }

                obs_found = true;
                break;
            }
        }

        if !obs_found {
            warn!(
                "[PREFLIGHT] OBS installation not found in standard paths - this may cause issues"
            );
        }

        // Check for display (required for capture)
        #[cfg(target_os = "windows")]
        {
            info!("[PREFLIGHT] Checking for display availability...");

            // On Windows, check if we have a display
            use std::ptr;
            unsafe {
                let dc = winapi::um::winuser::GetDC(ptr::null_mut());
                if dc.is_null() {
                    warn!("[PREFLIGHT] No display context available - capture may fail");
                } else {
                    info!("[PREFLIGHT] Display context available");
                    winapi::um::winuser::ReleaseDC(ptr::null_mut(), dc);
                }
            }
        }

        info!("[PREFLIGHT] Pre-flight checks completed");

        Ok(())
    }

    /// Get OBS search paths based on platform.
    fn get_obs_search_paths(&self) -> Vec<std::path::PathBuf> {
        let mut paths = Vec::new();

        // Current directory
        paths.push(std::env::current_dir().unwrap_or_default().join("obs"));

        // libobs-bootstrapper default locations
        #[cfg(target_os = "windows")]
        {
            // Check AppData
            if let Some(app_data) = std::env::var_os("LOCALAPPDATA") {
                paths.push(std::path::PathBuf::from(app_data).join("obs-studio"));
            }
            if let Some(app_data) = std::env::var_os("APPDATA") {
                paths.push(std::path::PathBuf::from(app_data).join("obs-studio"));
            }
            // Program Files
            paths.push(std::path::PathBuf::from("C:\\Program Files\\obs-studio"));
            paths.push(std::path::PathBuf::from(
                "C:\\Program Files (x86)\\obs-studio",
            ));
        }

        #[cfg(target_os = "linux")]
        {
            paths.push(std::path::PathBuf::from("/usr/share/obs"));
            paths.push(std::path::PathBuf::from("/usr/local/share/obs"));
            if let Some(home) = std::env::var_os("HOME") {
                paths.push(std::path::PathBuf::from(home).join(".local/share/obs"));
            }
        }

        paths
    }

    /// Start recording to the specified output path.
    pub fn start_recording(&mut self, output_path: &Path) -> Result<()> {
        info!(
            "[START_REC] start_recording called with path: {:?}",
            output_path
        );

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

        if self.context.is_none() {
            info!("[START_REC] OBS context not initialized, initializing now...");

            self.initialize()?;
            info!("[START_REC] OBS initialization complete");
        }

        let context = self.context.as_ref().ok_or_else(|| {
            error!("[START_REC] OBS not initialized after initialize()");

            RecordingError::ObsInitError("OBS not initialized".to_string())
        })?;

        info!("[START_REC] Starting OBS recording to: {:?}", output_path);

        // Get bitrate from encoder preset
        let bitrate = self.video_settings.encoder_preset.effective_bitrate();
        info!("[START_REC] Using bitrate: {} kbps", bitrate);

        // Create output path
        let path_str = output_path.to_string_lossy();
        let obs_path = ObsPath::new(&path_str);

        // Get detected encoder capability
        let encoder = self
            .encoder_capability
            .unwrap_or_else(best_available_encoder);
        info!("[START_REC] Using encoder: {}", encoder.name());

        // Build the output based on encoder capability
        info!("[START_REC] Creating SimpleOutputBuilder...");

        let output_result = match encoder {
            EncoderCapability::Nvenc => {
                info!("Building NVENC output...");
                SimpleOutputBuilder::new(context.clone(), ObsString::from("output"), obs_path)
                    .video_bitrate(bitrate)
                    .audio_bitrate(self.audio_settings.bitrate)
                    .hardware_encoder(HardwareCodec::H264, HardwarePreset::Speed)
                    .format(OutputFormat::Mpeg4)
                    .build()
            }
            EncoderCapability::Amf => {
                info!("Building AMF output...");
                SimpleOutputBuilder::new(context.clone(), ObsString::from("output"), obs_path)
                    .video_bitrate(bitrate)
                    .audio_bitrate(self.audio_settings.bitrate)
                    .hardware_encoder(HardwareCodec::H264, HardwarePreset::Balanced)
                    .format(OutputFormat::Mpeg4)
                    .build()
            }
            EncoderCapability::QuickSync => {
                info!("Building QuickSync output...");
                SimpleOutputBuilder::new(context.clone(), ObsString::from("output"), obs_path)
                    .video_bitrate(bitrate)
                    .audio_bitrate(self.audio_settings.bitrate)
                    .hardware_encoder(HardwareCodec::H264, HardwarePreset::Balanced)
                    .format(OutputFormat::Mpeg4)
                    .build()
            }
            EncoderCapability::Software => {
                info!("Building software (x264) output...");
                SimpleOutputBuilder::new(context.clone(), ObsString::from("output"), obs_path)
                    .video_bitrate(bitrate)
                    .audio_bitrate(self.audio_settings.bitrate)
                    .format(OutputFormat::Mpeg4)
                    .build()
            }
        };

        info!("[START_REC] Output build complete, checking for errors...");

        let output = output_result.map_err(|e| {
            error!("[START_REC] Failed to create output: {:?}", e);

            RecordingError::StartError(format!("Failed to create output: {:?}", e))
        })?;

        info!("[START_REC] Output created successfully, setting up game capture...");

        // Set up game capture source
        self.setup_game_capture()?;

        // Set up audio capture source
        if self.audio_settings.enabled && self.audio_settings.capture_game {
            info!("[START_REC] Setting up audio capture...");
            self.setup_audio_capture()?;
            info!("[START_REC] Audio capture set up successfully");
        }

        info!("[START_REC] Game capture set up, starting output...");

        // Start the output - wrap in catch_unwind as this may crash in native code
        let start_result =
            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| output.start()));

        match start_result {
            Ok(Ok(())) => {
                info!("[START_REC] Output started successfully");
            }
            Ok(Err(e)) => {
                error!("[START_REC] Failed to start output: {:?}", e);

                return Err(
                    RecordingError::StartError(format!("Failed to start output: {:?}", e)).into(),
                );
            }
            Err(panic_info) => {
                error!("[START_REC] PANIC starting output: {:?}", panic_info);

                eprintln!("[START_REC] PANIC starting output: {:?}", panic_info);
                return Err(RecordingError::StartError("Panic starting output".to_string()).into());
            }
        }

        self.output = Some(output);
        self.current_output = Some(output_path.to_path_buf());
        self.recording = true;

        info!("[START_REC] OBS recording started successfully");

        Ok(())
    }

    /// Set up capture source with fallback from game capture to monitor capture.
    #[cfg(target_os = "windows")]
    fn setup_game_capture(&mut self) -> Result<()> {
        info!("[CAPTURE] Setting up capture source...");

        // Try game capture first, fall back to monitor capture
        match self.try_game_capture() {
            Ok(()) => {
                info!("[CAPTURE] Game capture set up successfully");

                Ok(())
            }
            Err(e) => {
                warn!(
                    "[CAPTURE] Game capture failed: {}, falling back to monitor capture",
                    e
                );

                self.setup_monitor_capture()
            }
        }
    }

    /// Set up capture source on Linux using screen capture.
    #[cfg(target_os = "linux")]
    fn setup_game_capture(&mut self) -> Result<()> {
        info!("[CAPTURE] Setting up screen capture for Linux...");

        self.setup_linux_screen_capture()
    }

    /// Try to set up game capture source (Windows only).
    #[cfg(target_os = "windows")]
    fn try_game_capture(&mut self) -> Result<()> {
        use libobs_simple::sources::windows::{GameCaptureSourceBuilder, ObsGameCaptureMode};
        use libobs_simple::sources::ObsSourceBuilder;

        info!("[GAME_CAPTURE] Attempting game capture setup...");

        let context = self.context.as_mut().ok_or_else(|| {
            error!("[GAME_CAPTURE] OBS not initialized in setup_game_capture");

            RecordingError::ObsInitError("OBS not initialized".to_string())
        })?;

        // Create a scene
        info!("[GAME_CAPTURE] Creating scene 'main'...");

        let mut scene = context.scene("main", None).map_err(|e| {
            error!("[GAME_CAPTURE] Failed to create scene: {:?}", e);

            RecordingError::StartError(format!("Failed to create scene: {:?}", e))
        })?;
        info!("[GAME_CAPTURE] Scene created successfully");

        // Build game capture source
        info!("[GAME_CAPTURE] Getting OBS runtime...");

        let runtime = context.runtime();

        info!("[GAME_CAPTURE] Creating game capture source builder...");

        // Wrap game capture builder in catch_unwind as it may crash in native code
        let builder_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
            GameCaptureSourceBuilder::new("game_capture", runtime.clone())
        }));

        let builder = match builder_result {
            Ok(Ok(b)) => {
                info!("[GAME_CAPTURE] Game capture builder created");

                b
            }
            Ok(Err(e)) => {
                error!(
                    "[GAME_CAPTURE] Failed to create game capture builder: {:?}",
                    e
                );

                return Err(RecordingError::StartError(format!(
                    "Failed to create game capture builder: {:?}",
                    e
                ))
                .into());
            }
            Err(panic_info) => {
                error!(
                    "[GAME_CAPTURE] PANIC creating game capture builder: {:?}",
                    panic_info
                );

                eprintln!(
                    "[GAME_CAPTURE] PANIC creating game capture builder: {:?}",
                    panic_info
                );
                return Err(RecordingError::StartError(
                    "Panic creating game capture builder".to_string(),
                )
                .into());
            }
        };

        info!("[GAME_CAPTURE] Configuring game capture for League of Legends...");

        // Use "Any" mode to capture any fullscreen application
        // This is the most reliable mode for games like League of Legends
        info!("[GAME_CAPTURE] Using 'Any' mode to capture fullscreen games...");

        // Wrap source build in catch_unwind
        // Enable capture_audio so the game capture source automatically creates
        // an "Application Audio Capture" (WASAPI) source for the captured game.
        let source_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
            builder
                .set_capture_mode(ObsGameCaptureMode::Any)
                .set_capture_audio(true)
                .expect("[GAME_CAPTURE] Audio capture not available on this system")
                .build()
        }));

        let source = match source_result {
            Ok(Ok(s)) => {
                info!("[GAME_CAPTURE] Game capture source created");

                s
            }
            Ok(Err(e)) => {
                error!(
                    "[GAME_CAPTURE] Failed to create game capture source: {:?}",
                    e
                );

                return Err(RecordingError::StartError(format!(
                    "Failed to create game capture source: {:?}",
                    e
                ))
                .into());
            }
            Err(panic_info) => {
                error!(
                    "[GAME_CAPTURE] PANIC creating game capture source: {:?}",
                    panic_info
                );

                eprintln!(
                    "[GAME_CAPTURE] PANIC creating game capture source: {:?}",
                    panic_info
                );
                return Err(RecordingError::StartError(
                    "Panic creating game capture source".to_string(),
                )
                .into());
            }
        };

        // Add source to scene using add_source
        info!("[GAME_CAPTURE] Adding source to scene...");

        scene.add_source(source).map_err(|e| {
            error!("[GAME_CAPTURE] Failed to add source to scene: {:?}", e);

            RecordingError::StartError(format!("Failed to add source to scene: {:?}", e))
        })?;
        info!("[GAME_CAPTURE] Source added to scene");

        // Set the scene as active
        info!("[GAME_CAPTURE] Setting scene as active on channel 0...");

        scene.set_to_channel(0).map_err(|e| {
            error!("[GAME_CAPTURE] Failed to set scene: {:?}", e);
            RecordingError::StartError(format!("Failed to set scene: {:?}", e))
        })?;

        info!("[GAME_CAPTURE] Game capture source configured successfully");

        Ok(())
    }

    /// Set up monitor capture as fallback (Windows only).
    #[cfg(target_os = "windows")]
    fn setup_monitor_capture(&mut self) -> Result<()> {
        use libobs_simple::sources::windows::MonitorCaptureSourceBuilder;
        use libobs_simple::sources::ObsSourceBuilder;

        info!("[MONITOR_CAPTURE] Setting up monitor capture as fallback...");

        let context = self.context.as_mut().ok_or_else(|| {
            error!("[MONITOR_CAPTURE] OBS not initialized");

            RecordingError::ObsInitError("OBS not initialized".to_string())
        })?;

        // Create a scene
        info!("[MONITOR_CAPTURE] Creating scene 'main'...");

        let mut scene = context.scene("main", None).map_err(|e| {
            error!("[MONITOR_CAPTURE] Failed to create scene: {:?}", e);

            RecordingError::StartError(format!("Failed to create scene: {:?}", e))
        })?;
        info!("[MONITOR_CAPTURE] Scene created successfully");

        // Get monitor info
        info!("[MONITOR_CAPTURE] Detecting monitors...");

        let monitors = display_info::DisplayInfo::all().map_err(|e| {
            error!("[MONITOR_CAPTURE] Failed to get display info: {:?}", e);

            RecordingError::StartError(format!("Failed to get display info: {:?}", e))
        })?;

        if monitors.is_empty() {
            error!("[MONITOR_CAPTURE] No monitors detected");

            return Err(RecordingError::StartError("No monitors detected".to_string()).into());
        }

        // Use the primary monitor (first in the list)
        let primary_monitor = &monitors[0];
        info!(
            "[MONITOR_CAPTURE] Using monitor: {}x{} at ({}, {})",
            primary_monitor.width, primary_monitor.height, primary_monitor.x, primary_monitor.y
        );

        // Build monitor capture source
        info!("[MONITOR_CAPTURE] Creating monitor capture source builder...");

        let builder_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
            MonitorCaptureSourceBuilder::new("monitor_capture", context.runtime().clone())
        }));

        let builder = match builder_result {
            Ok(Ok(b)) => {
                info!("[MONITOR_CAPTURE] Monitor capture builder created");

                b
            }
            Ok(Err(e)) => {
                error!(
                    "[MONITOR_CAPTURE] Failed to create monitor capture builder: {:?}",
                    e
                );

                return Err(RecordingError::StartError(format!(
                    "Failed to create monitor capture builder: {:?}",
                    e
                ))
                .into());
            }
            Err(panic_info) => {
                error!(
                    "[MONITOR_CAPTURE] PANIC creating monitor capture builder: {:?}",
                    panic_info
                );

                eprintln!(
                    "[MONITOR_CAPTURE] PANIC creating monitor capture builder: {:?}",
                    panic_info
                );
                return Err(RecordingError::StartError(
                    "Panic creating monitor capture builder".to_string(),
                )
                .into());
            }
        };

        // Build the source
        let source_result =
            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| builder.build()));

        let source = match source_result {
            Ok(Ok(s)) => {
                info!("[MONITOR_CAPTURE] Monitor capture source created");

                s
            }
            Ok(Err(e)) => {
                error!(
                    "[MONITOR_CAPTURE] Failed to create monitor capture source: {:?}",
                    e
                );

                return Err(RecordingError::StartError(format!(
                    "Failed to create monitor capture source: {:?}",
                    e
                ))
                .into());
            }
            Err(panic_info) => {
                error!(
                    "[MONITOR_CAPTURE] PANIC creating monitor capture source: {:?}",
                    panic_info
                );

                eprintln!(
                    "[MONITOR_CAPTURE] PANIC creating monitor capture source: {:?}",
                    panic_info
                );
                return Err(RecordingError::StartError(
                    "Panic creating monitor capture source".to_string(),
                )
                .into());
            }
        };

        // Add source to scene
        info!("[MONITOR_CAPTURE] Adding source to scene...");

        scene.add_source(source).map_err(|e| {
            error!("[MONITOR_CAPTURE] Failed to add source to scene: {:?}", e);

            RecordingError::StartError(format!("Failed to add source to scene: {:?}", e))
        })?;
        info!("[MONITOR_CAPTURE] Source added to scene");

        // Set the scene as active
        info!("[MONITOR_CAPTURE] Setting scene as active on channel 0...");

        scene.set_to_channel(0).map_err(|e| {
            error!("[MONITOR_CAPTURE] Failed to set scene: {:?}", e);
            RecordingError::StartError(format!("Failed to set scene: {:?}", e))
        })?;

        info!("[MONITOR_CAPTURE] Monitor capture source configured successfully");

        Ok(())
    }

    /// Set up screen capture on Linux using X11 or PipeWire.
    #[cfg(target_os = "linux")]
    fn setup_linux_screen_capture(&mut self) -> Result<()> {
        use libobs_simple::sources::linux::LinuxGeneralScreenCaptureBuilder;
        use libobs_simple::sources::ObsSourceBuilder;

        info!("[LINUX_CAPTURE] Setting up Linux screen capture...");

        let context = self.context.as_mut().ok_or_else(|| {
            error!("[LINUX_CAPTURE] OBS not initialized");

            RecordingError::ObsInitError("OBS not initialized".to_string())
        })?;

        // Create a scene
        info!("[LINUX_CAPTURE] Creating scene 'main'...");

        let mut scene = context.scene("main", None).map_err(|e| {
            error!("[LINUX_CAPTURE] Failed to create scene: {:?}", e);

            RecordingError::StartError(format!("Failed to create scene: {:?}", e))
        })?;
        info!("[LINUX_CAPTURE] Scene created successfully");

        // Build screen capture source using LinuxGeneralScreenCaptureBuilder
        // This automatically uses X11 or PipeWire depending on the platform
        info!("[LINUX_CAPTURE] Creating screen capture source builder...");

        let builder_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
            LinuxGeneralScreenCaptureBuilder::new("screen_capture", context.runtime().clone())
        }));

        let builder = match builder_result {
            Ok(Ok(b)) => {
                info!("[LINUX_CAPTURE] Screen capture builder created");

                b
            }
            Ok(Err(e)) => {
                error!(
                    "[LINUX_CAPTURE] Failed to create screen capture builder: {:?}",
                    e
                );

                return Err(RecordingError::StartError(format!(
                    "Failed to create screen capture builder: {:?}",
                    e
                ))
                .into());
            }
            Err(panic_info) => {
                error!(
                    "[LINUX_CAPTURE] PANIC creating screen capture builder: {:?}",
                    panic_info
                );

                eprintln!(
                    "[LINUX_CAPTURE] PANIC creating screen capture builder: {:?}",
                    panic_info
                );
                return Err(RecordingError::StartError(
                    "Panic creating screen capture builder".to_string(),
                )
                .into());
            }
        };

        // Build the source
        let source_result =
            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| builder.build()));

        let source = match source_result {
            Ok(Ok(s)) => {
                info!("[LINUX_CAPTURE] Screen capture source created");

                s
            }
            Ok(Err(e)) => {
                error!(
                    "[LINUX_CAPTURE] Failed to create screen capture source: {:?}",
                    e
                );

                return Err(RecordingError::StartError(format!(
                    "Failed to create screen capture source: {:?}",
                    e
                ))
                .into());
            }
            Err(panic_info) => {
                error!(
                    "[LINUX_CAPTURE] PANIC creating screen capture source: {:?}",
                    panic_info
                );

                eprintln!(
                    "[LINUX_CAPTURE] PANIC creating screen capture source: {:?}",
                    panic_info
                );
                return Err(RecordingError::StartError(
                    "Panic creating screen capture source".to_string(),
                )
                .into());
            }
        };

        // Add source to scene
        info!("[LINUX_CAPTURE] Adding source to scene...");

        scene.add_source(source).map_err(|e| {
            error!("[LINUX_CAPTURE] Failed to add source to scene: {:?}", e);

            RecordingError::StartError(format!("Failed to add source to scene: {:?}", e))
        })?;
        info!("[LINUX_CAPTURE] Source added to scene");

        // Set the scene as active
        info!("[LINUX_CAPTURE] Setting scene as active on channel 0...");

        scene.set_to_channel(0).map_err(|e| {
            error!("[LINUX_CAPTURE] Failed to set scene: {:?}", e);
            RecordingError::StartError(format!("Failed to set scene: {:?}", e))
        })?;

        info!("[LINUX_CAPTURE] Screen capture source configured successfully");

        Ok(())
    }

    /// Set up audio capture for game audio (Windows only).
    #[cfg(target_os = "windows")]
    fn setup_audio_capture(&mut self) -> Result<()> {
        use libobs_wrapper::data::ObsData;
        use libobs_wrapper::utils::SourceInfo;

        info!("[AUDIO_CAPTURE] Setting up WASAPI audio capture...");

        let context = self.context.as_mut().ok_or_else(|| {
            error!("[AUDIO_CAPTURE] OBS not initialized");
            RecordingError::ObsInitError("OBS not initialized".to_string())
        })?;

        // Create audio source settings using JSON for reliable string handling
        let settings_json = r#"{
            "executable": "League of Legends.exe",
            "title": "League of Legends (TM) Client",
            "class": "RiotWindowClass",
            "priority": 2
        }"#;

        let audio_settings_data = ObsData::from_json(settings_json, context.runtime().clone())
            .map_err(|e| {
                error!(
                    "[AUDIO_CAPTURE] Failed to create audio settings from JSON: {:?}",
                    e
                );
                RecordingError::StartError(format!(
                    "Failed to create audio settings from JSON: {:?}",
                    e
                ))
            })?;

        info!("[AUDIO_CAPTURE] Audio settings configured for League of Legends");

        // Configure WASAPI process output capture for game audio
        // This captures the audio output from a specific process (the game)
        let source_info = SourceInfo::new(
            "wasapi_process_output_capture",
            "game_audio",
            Some(audio_settings_data),
            None,
        );

        info!("[AUDIO_CAPTURE] Creating WASAPI process output capture source...");

        let audio_source_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
            libobs_wrapper::sources::ObsSourceRef::new_from_info(
                source_info,
                context.runtime().clone(),
            )
        }));

        let _audio_source = match audio_source_result {
            Ok(Ok(s)) => {
                info!("[AUDIO_CAPTURE] WASAPI audio source created");
                s
            }
            Ok(Err(e)) => {
                error!("[AUDIO_CAPTURE] Failed to create audio source: {:?}", e);
                return Err(RecordingError::StartError(format!(
                    "Failed to create audio source: {:?}",
                    e
                ))
                .into());
            }
            Err(panic_info) => {
                error!(
                    "[AUDIO_CAPTURE] PANIC creating audio source: {:?}",
                    panic_info
                );
                eprintln!(
                    "[AUDIO_CAPTURE] PANIC creating audio source: {:?}",
                    panic_info
                );
                return Err(
                    RecordingError::StartError("Panic creating audio source".to_string()).into(),
                );
            }
        };

        info!("[AUDIO_CAPTURE] Audio source created successfully");

        Ok(())
    }

    /// Set up audio capture for game audio (Linux only).
    #[cfg(target_os = "linux")]
    fn setup_audio_capture(&mut self) -> Result<()> {
        use libobs_wrapper::utils::SourceInfo;

        info!("[AUDIO_CAPTURE] Setting up PulseAudio capture...");

        let context = self.context.as_mut().ok_or_else(|| {
            error!("[AUDIO_CAPTURE] OBS not initialized");
            RecordingError::ObsInitError("OBS not initialized".to_string())
        })?;

        // Create audio source settings
        let audio_settings_data = context.data().map_err(|e| {
            error!("[AUDIO_CAPTURE] Failed to create audio settings: {:?}", e);
            RecordingError::StartError(format!("Failed to create audio settings: {:?}", e))
        })?;

        // Configure PulseAudio capture for game audio on Linux
        let source_info = SourceInfo::new(
            "pulse_input_capture",
            "game_audio",
            Some(audio_settings_data),
            None,
        );

        info!("[AUDIO_CAPTURE] Creating PulseAudio capture source...");

        let audio_source_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
            libobs_wrapper::sources::ObsSourceRef::new_from_info(
                source_info,
                context.runtime().clone(),
            )
        }));

        let _audio_source = match audio_source_result {
            Ok(Ok(s)) => {
                info!("[AUDIO_CAPTURE] PulseAudio source created");
                s
            }
            Ok(Err(e)) => {
                error!("[AUDIO_CAPTURE] Failed to create audio source: {:?}", e);
                return Err(RecordingError::StartError(format!(
                    "Failed to create audio source: {:?}",
                    e
                ))
                .into());
            }
            Err(panic_info) => {
                error!(
                    "[AUDIO_CAPTURE] PANIC creating audio source: {:?}",
                    panic_info
                );
                eprintln!(
                    "[AUDIO_CAPTURE] PANIC creating audio source: {:?}",
                    panic_info
                );
                return Err(
                    RecordingError::StartError("Panic creating audio source".to_string()).into(),
                );
            }
        };

        info!("[AUDIO_CAPTURE] Audio source created successfully");

        Ok(())
    }

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

        info!("Stopping OBS recording");

        if let Some(mut output) = self.output.take() {
            output.stop().map_err(|e| {
                RecordingError::StopError(format!("Failed to stop output: {:?}", e))
            })?;
        }

        self.recording = false;
        self.current_output = None;

        info!("OBS recording stopped successfully");
        Ok(())
    }

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

        if self.output.is_some() {
            self.output = None;
        }

        if self.context.is_some() {
            info!("Shutting down OBS context");
            // The LibObsContext handles cleanup on drop
            self.context = None;
        }

        Ok(())
    }
}

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

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

    #[test]
    fn test_obs_context_builder() {
        let context = ObsContextBuilder::new()
            .with_video_settings(VideoSettings::default())
            .with_audio_settings(AudioSettings::default())
            .build()
            .unwrap();

        assert!(!context.is_initialized());
        assert!(!context.is_recording());
    }
}
