//! Video and audio encoder configuration.
//!
//! This module provides encoder configuration types that integrate with
//! libobs-simple for hardware and software encoding.

use serde::{Deserialize, Serialize};

use crate::config::{AmfQuality, EncoderPreset, QualityLevel};

/// Video encoder configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EncoderConfig {
    /// Encoder ID (e.g., "jim_nvenc", "x264").
    pub encoder_id: String,
    /// Target bitrate in kbps.
    pub bitrate: u32,
    /// Keyframe interval in seconds.
    pub keyframe_interval: u32,
    /// Encoder-specific settings.
    pub settings: EncoderSettings,
}

impl Default for EncoderConfig {
    fn default() -> Self {
        Self {
            encoder_id: "jim_nvenc".to_string(),
            bitrate: 6000,
            keyframe_interval: 2,
            settings: EncoderSettings::Nvenc {
                cq_level: 20,
                two_pass: true,
                preset: "p4".to_string(),
                rate_control: NvencRateControl::Vbr,
            },
        }
    }
}

/// Encoder-specific settings.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum EncoderSettings {
    /// NVIDIA NVENC settings.
    Nvenc {
        /// Constant Quality level (0-51, lower = better).
        cq_level: u32,
        /// Use two-pass encoding.
        two_pass: bool,
        /// Preset (p1-p7, lower = faster).
        preset: String,
        /// Rate control mode.
        rate_control: NvencRateControl,
    },
    /// AMD AMF settings.
    Amf {
        /// Quality preset.
        quality: AmfQuality,
        /// Rate control method.
        rate_control: AmfRateControl,
    },
    /// x264 settings.
    X264 {
        /// Preset name.
        preset: String,
        /// Constant Rate Factor (if using CRF).
        crf: Option<u32>,
        /// Use threads.
        threads: u32,
    },
}

/// NVENC rate control modes.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum NvencRateControl {
    /// Constant bitrate.
    Cbr,
    /// Variable bitrate.
    #[default]
    Vbr,
    /// Constant quality.
    Cqp,
}

/// AMF rate control modes.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum AmfRateControl {
    /// Constant bitrate.
    Cbr,
    /// Variable bitrate.
    #[default]
    Vbr,
    /// Constant QP.
    Cqp,
}

impl EncoderConfig {
    /// Create encoder config from preset.
    pub fn from_preset(preset: &EncoderPreset) -> Self {
        match preset {
            EncoderPreset::Nvenc {
                bitrate,
                cq_level,
                two_pass,
            } => Self {
                encoder_id: "jim_nvenc".to_string(),
                bitrate: *bitrate,
                keyframe_interval: 2,
                settings: EncoderSettings::Nvenc {
                    cq_level: *cq_level,
                    two_pass: *two_pass,
                    preset: "p4".to_string(), // Balanced preset
                    rate_control: NvencRateControl::Vbr,
                },
            },
            EncoderPreset::Amf { bitrate, quality } => Self {
                encoder_id: "amd_amf_h264".to_string(),
                bitrate: *bitrate,
                keyframe_interval: 2,
                settings: EncoderSettings::Amf {
                    quality: *quality,
                    rate_control: AmfRateControl::Vbr,
                },
            },
            EncoderPreset::X264 {
                preset,
                bitrate,
                crf,
            } => Self {
                encoder_id: "x264".to_string(),
                bitrate: *bitrate,
                keyframe_interval: 2,
                settings: EncoderSettings::X264 {
                    preset: preset.clone(),
                    crf: *crf,
                    threads: 0, // Auto
                },
            },
        }
    }

    /// Get the encoder name for display.
    pub fn encoder_name(&self) -> &str {
        &self.encoder_id
    }

    /// Check if this is a hardware encoder.
    pub fn is_hardware(&self) -> bool {
        matches!(
            self.settings,
            EncoderSettings::Nvenc { .. } | EncoderSettings::Amf { .. }
        )
    }

    /// Check if this is an NVIDIA encoder.
    pub fn is_nvenc(&self) -> bool {
        matches!(self.settings, EncoderSettings::Nvenc { .. })
    }

    /// Check if this is an AMD encoder.
    pub fn is_amf(&self) -> bool {
        matches!(self.settings, EncoderSettings::Amf { .. })
    }

    /// Check if this is a software encoder.
    pub fn is_software(&self) -> bool {
        matches!(self.settings, EncoderSettings::X264 { .. })
    }

    /// Get the hardware codec type for libobs-simple.
    pub fn hardware_codec(&self) -> Option<libobs_simple::output::simple::HardwareCodec> {
        use libobs_simple::output::simple::HardwareCodec;

        match &self.settings {
            EncoderSettings::Nvenc { .. } => Some(HardwareCodec::H264),
            EncoderSettings::Amf { .. } => Some(HardwareCodec::H264),
            EncoderSettings::X264 { .. } => None,
        }
    }

    /// Get the hardware preset for libobs-simple.
    pub fn hardware_preset(&self) -> libobs_simple::output::simple::HardwarePreset {
        use libobs_simple::output::simple::HardwarePreset;

        match &self.settings {
            EncoderSettings::Nvenc { preset, .. } => match preset.as_str() {
                "p1" => HardwarePreset::Speed,
                "p2" => HardwarePreset::Speed,
                "p3" => HardwarePreset::Balanced,
                "p4" => HardwarePreset::Balanced,
                "p5" => HardwarePreset::Quality,
                "p6" => HardwarePreset::Quality,
                "p7" => HardwarePreset::Quality,
                _ => HardwarePreset::Balanced,
            },
            EncoderSettings::Amf { quality, .. } => match quality {
                AmfQuality::Speed => HardwarePreset::Speed,
                AmfQuality::Balanced => HardwarePreset::Balanced,
                AmfQuality::Quality => HardwarePreset::Quality,
            },
            EncoderSettings::X264 { .. } => HardwarePreset::Balanced,
        }
    }
}

/// Audio encoder configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AudioEncoderConfig {
    /// Encoder ID (usually "ffmpeg_aac").
    pub encoder_id: String,
    /// Bitrate in kbps.
    pub bitrate: u32,
    /// Sample rate in Hz.
    pub sample_rate: u32,
    /// Number of channels.
    pub channels: u32,
}

impl Default for AudioEncoderConfig {
    fn default() -> Self {
        Self {
            encoder_id: "ffmpeg_aac".to_string(),
            bitrate: 192,
            sample_rate: 48000,
            channels: 2,
        }
    }
}

impl AudioEncoderConfig {
    /// Create a new audio encoder config with the specified bitrate.
    pub fn with_bitrate(bitrate: u32) -> Self {
        Self {
            bitrate,
            ..Default::default()
        }
    }
}

/// Encoder capability.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EncoderCapability {
    /// NVIDIA NVENC.
    Nvenc,
    /// AMD AMF.
    Amf,
    /// Intel QuickSync.
    QuickSync,
    /// Software (x264).
    Software,
}

impl EncoderCapability {
    /// Get the recommended encoder preset for this capability.
    pub fn recommended_preset(&self, quality: QualityLevel) -> EncoderPreset {
        let bitrate = quality.recommended_bitrate();

        match self {
            EncoderCapability::Nvenc => EncoderPreset::Nvenc {
                bitrate,
                cq_level: 20,
                two_pass: true,
            },
            EncoderCapability::Amf => EncoderPreset::Amf {
                bitrate,
                quality: AmfQuality::Balanced,
            },
            EncoderCapability::QuickSync => EncoderPreset::X264 {
                preset: "veryfast".to_string(),
                bitrate,
                crf: None,
            },
            EncoderCapability::Software => EncoderPreset::X264 {
                preset: "superfast".to_string(),
                bitrate: (bitrate as f32 * 0.8) as u32,
                crf: Some(23),
            },
        }
    }

    /// Get a human-readable name for this encoder capability.
    pub fn name(&self) -> &'static str {
        match self {
            EncoderCapability::Nvenc => "NVIDIA NVENC",
            EncoderCapability::Amf => "AMD AMF",
            EncoderCapability::QuickSync => "Intel QuickSync",
            EncoderCapability::Software => "Software (x264)",
        }
    }
}

/// Detect available hardware encoders.
///
/// This function checks the system for available GPU encoders.
pub fn detect_hardware_encoders() -> Vec<EncoderCapability> {
    use tracing::info;

    info!("[ENCODER_DETECT] Starting hardware encoder detection...");

    // Software encoding is always available=
    let capabilities = vec![EncoderCapability::Software];

    #[cfg(target_os = "linux")]
    {
        let _capabilities = [EncoderCapability::Software];
    }

    #[cfg(target_os = "windows")]
    {
        let mut capabilities = vec![EncoderCapability::Software];
        let gpu_vendors = detect_gpu_vendors();

        if gpu_vendors.contains(&GpuVendor::Nvidia) {
            info!("[ENCODER_DETECT] NVENC available");
            capabilities.push(EncoderCapability::Nvenc);
        } else {
            info!("[ENCODER_DETECT] NVENC not available");
        }

        if gpu_vendors.contains(&GpuVendor::Amd) {
            info!("[ENCODER_DETECT] AMF available");
            capabilities.push(EncoderCapability::Amf);
        } else {
            info!("[ENCODER_DETECT] AMF not available");
        }

        if gpu_vendors.contains(&GpuVendor::Intel) {
            info!("[ENCODER_DETECT] QuickSync available");
            capabilities.push(EncoderCapability::QuickSync);
        } else {
            info!("[ENCODER_DETECT] QuickSync not available");
        }
    }

    info!("[ENCODER_DETECT] Detected encoders: {:?}", capabilities);

    capabilities
}

/// GPU vendor type for encoder detection.
#[cfg(target_os = "windows")]
#[derive(Debug, Clone, Copy, PartialEq)]
enum GpuVendor {
    Nvidia,
    Amd,
    Intel,
}

/// Detect GPU vendors by querying Windows registry.
/// This is more reliable than checking for specific DLL paths.
#[cfg(target_os = "windows")]
fn detect_gpu_vendors() -> Vec<GpuVendor> {
    use tracing::info;
    use winreg::enums::HKEY_LOCAL_MACHINE;
    use winreg::RegKey;

    let mut vendors = Vec::new();

    // Display adapter class GUID in Windows registry
    const DISPLAY_ADAPTER_CLASS: &str =
        r"SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}";

    info!("[ENCODER_DETECT] Querying Windows registry for GPU vendors...");

    // Open the display adapter class key
    let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);

    let class_key = match hklm.open_subkey(DISPLAY_ADAPTER_CLASS) {
        Ok(key) => key,
        Err(e) => {
            info!(
                "[ENCODER_DETECT] Failed to open display adapter registry key: {}",
                e
            );
            return vendors;
        }
    };

    // Enumerate subkeys (each subkey is a display adapter)
    for subkey_name in class_key.enum_keys().filter_map(|k| k.ok()) {
        if let Ok(adapter_key) = class_key.open_subkey(&subkey_name) {
            // Read ProviderName
            if let Ok(provider) = adapter_key.get_value::<String, _>("ProviderName") {
                info!("[ENCODER_DETECT] Found GPU provider: {}", provider);

                let provider_lower = provider.to_lowercase();
                if provider_lower.contains("nvidia") && !vendors.contains(&GpuVendor::Nvidia) {
                    vendors.push(GpuVendor::Nvidia);
                } else if (provider_lower.contains("amd")
                    || provider_lower.contains("advanced micro devices")
                    || provider_lower.contains("radeon"))
                    && !vendors.contains(&GpuVendor::Amd)
                {
                    vendors.push(GpuVendor::Amd);
                } else if provider_lower.contains("intel") && !vendors.contains(&GpuVendor::Intel) {
                    vendors.push(GpuVendor::Intel);
                }
            }
        }
    }

    info!("[ENCODER_DETECT] Detected GPU vendors: {:?}", vendors);
    vendors
}

/// Get the best available encoder capability.
pub fn best_available_encoder() -> EncoderCapability {
    let capabilities = detect_hardware_encoders();

    // Prefer hardware encoders
    for cap in &capabilities {
        if matches!(
            cap,
            EncoderCapability::Nvenc | EncoderCapability::Amf | EncoderCapability::QuickSync
        ) {
            return *cap;
        }
    }

    // Fall back to software
    EncoderCapability::Software
}

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

    #[test]
    fn test_encoder_config_from_nvenc_preset() {
        let preset = EncoderPreset::Nvenc {
            bitrate: 8000,
            cq_level: 20,
            two_pass: true,
        };

        let config = EncoderConfig::from_preset(&preset);

        assert_eq!(config.encoder_id, "jim_nvenc");
        assert_eq!(config.bitrate, 8000);
        assert!(config.is_hardware());
        assert!(config.is_nvenc());
    }

    #[test]
    fn test_encoder_config_from_x264_preset() {
        let preset = EncoderPreset::X264 {
            preset: "fast".to_string(),
            bitrate: 6000,
            crf: Some(23),
        };

        let config = EncoderConfig::from_preset(&preset);

        assert_eq!(config.encoder_id, "x264");
        assert!(!config.is_hardware());
        assert!(config.is_software());
    }

    #[test]
    fn test_detect_hardware_encoders() {
        let capabilities = detect_hardware_encoders();
        assert!(!capabilities.is_empty());
        assert!(capabilities.contains(&EncoderCapability::Software));
    }

    #[test]
    fn test_best_available_encoder() {
        let best = best_available_encoder();
        // Should always return something
        assert!(matches!(
            best,
            EncoderCapability::Nvenc
                | EncoderCapability::Amf
                | EncoderCapability::QuickSync
                | EncoderCapability::Software
        ));
    }
}
