//! IPC command handlers.

use std::sync::Arc;

use async_trait::async_trait;
use parking_lot::RwLock;
use tracing::{debug, info};

use super::protocol::{
    EncoderInfo, GetEncodersResponse, GetRecordingsResponse, GetSettingsResponse,
    GetStatusResponse, IpcCommand, IpcMessage, IpcResponse,
};
use crate::config::Settings;
use crate::error::Result;
use crate::recording::encoder::{detect_hardware_encoders, EncoderCapability};
use crate::recording::RecordingEngine;
use crate::state::DaemonStatus;
use crate::timeline::TimelineStore;

/// Command handler trait.
#[async_trait]
pub trait CommandHandler: Send + Sync {
    /// Handle the command and return a response.
    async fn handle(&self, payload: Option<serde_json::Value>) -> Result<serde_json::Value>;
}

/// Collection of IPC command handlers.
pub struct IpcHandlers {
    /// Settings reference.
    settings: Arc<RwLock<Settings>>,
    /// Recording engine reference.
    recording: Arc<RwLock<Option<RecordingEngine>>>,
    /// Timeline store reference.
    timeline: Arc<RwLock<TimelineStore>>,
    /// Daemon status.
    status: Arc<RwLock<DaemonStatus>>,
    /// Client connection status.
    client_connected: Arc<RwLock<bool>>,
}

impl IpcHandlers {
    /// Create a new handlers collection.
    pub fn new(
        settings: Arc<RwLock<Settings>>,
        recording: Arc<RwLock<Option<RecordingEngine>>>,
        timeline: Arc<RwLock<TimelineStore>>,
        status: Arc<RwLock<DaemonStatus>>,
        client_connected: Arc<RwLock<bool>>,
    ) -> Self {
        Self {
            settings,
            recording,
            timeline,
            status,
            client_connected,
        }
    }

    /// Handle an incoming message.
    pub async fn handle(&self, message: IpcMessage) -> IpcResponse {
        let command = match message.command {
            Some(cmd) => cmd,
            None => return IpcResponse::error(message.id, "No command specified"),
        };

        debug!("Handling command: {:?}", command);

        let result = match command {
            IpcCommand::GetSettings => self.handle_get_settings().await,
            IpcCommand::UpdateSettings => self.handle_update_settings(message.payload).await,
            IpcCommand::ResetSettings => self.handle_reset_settings().await,
            IpcCommand::GetStatus => self.handle_get_status().await,
            IpcCommand::GetEncoders => self.handle_get_encoders().await,
            IpcCommand::StartRecording => self.handle_start_recording().await,
            IpcCommand::StopRecording => self.handle_stop_recording().await,
            IpcCommand::GetRecordings => self.handle_get_recordings().await,
            IpcCommand::GetRecording { id } => self.handle_get_recording(id).await,
            IpcCommand::DeleteRecording { id } => self.handle_delete_recording(id).await,
            IpcCommand::GetTimeline { recording_id } => {
                self.handle_get_timeline(recording_id).await
            }
            IpcCommand::ExportTimeline {
                recording_id,
                format,
            } => self.handle_export_timeline(recording_id, format).await,
            IpcCommand::Shutdown => self.handle_shutdown().await,
        };

        match result {
            Ok(data) => IpcResponse::success(message.id, data),
            Err(e) => {
                info!("Command failed: {}", e);
                IpcResponse::error(message.id, e.to_string())
            }
        }
    }

    async fn handle_get_settings(&self) -> Result<serde_json::Value> {
        let settings = self.settings.read().clone();
        let response = GetSettingsResponse { settings };
        Ok(serde_json::to_value(response)?)
    }

    async fn handle_update_settings(
        &self,
        payload: Option<serde_json::Value>,
    ) -> Result<serde_json::Value> {
        let payload = payload.ok_or_else(|| {
            crate::error::IpcError::InvalidCommand("Missing settings payload".to_string())
        })?;

        let new_settings: Settings = serde_json::from_value(payload)?;

        // Validate settings
        if new_settings.video.frame_rate == 0 || new_settings.video.frame_rate > 240 {
            return Err(
                crate::error::ConfigError::InvalidConfig("Invalid frame rate".to_string()).into(),
            );
        }

        // Update settings
        *self.settings.write() = new_settings.clone();

        // Persist settings
        crate::config::save_config(&new_settings)?;

        info!("Settings updated");
        Ok(serde_json::json!({ "success": true }))
    }

    async fn handle_reset_settings(&self) -> Result<serde_json::Value> {
        let default_settings = Settings::default();
        *self.settings.write() = default_settings.clone();

        crate::config::save_config(&default_settings)?;

        info!("Settings reset to defaults");
        Ok(serde_json::to_value(default_settings)?)
    }

    async fn handle_get_status(&self) -> Result<serde_json::Value> {
        let status = *self.status.read();
        let recording = self.recording.read();
        let is_recording = recording
            .as_ref()
            .map(|r| r.is_recording())
            .unwrap_or(false);
        let client_connected = *self.client_connected.read();
        let current_game_id = recording.as_ref().and_then(|r| r.current_game_id());

        let response = GetStatusResponse {
            status,
            is_recording,
            current_game_id,
            client_connected,
        };
        Ok(serde_json::to_value(response)?)
    }

    async fn handle_get_encoders(&self) -> Result<serde_json::Value> {
        let capabilities = detect_hardware_encoders();
        let current = self
            .settings
            .read()
            .video
            .encoder_preset
            .encoder_name()
            .to_string();

        let available: Vec<EncoderInfo> = capabilities
            .iter()
            .map(|cap| match cap {
                EncoderCapability::Nvenc => EncoderInfo {
                    id: "jim_nvenc".to_string(),
                    name: "NVIDIA NVENC".to_string(),
                    is_hardware: true,
                    available: true,
                },
                EncoderCapability::Amf => EncoderInfo {
                    id: "amd_amf_h264".to_string(),
                    name: "AMD AMF".to_string(),
                    is_hardware: true,
                    available: true,
                },
                EncoderCapability::QuickSync => EncoderInfo {
                    id: "qsv".to_string(),
                    name: "Intel QuickSync".to_string(),
                    is_hardware: true,
                    available: true,
                },
                EncoderCapability::Software => EncoderInfo {
                    id: "x264".to_string(),
                    name: "x264 (Software)".to_string(),
                    is_hardware: false,
                    available: true,
                },
            })
            .collect();

        let response = GetEncodersResponse { available, current };
        Ok(serde_json::to_value(response)?)
    }

    async fn handle_start_recording(&self) -> Result<serde_json::Value> {
        let mut recording_guard = self.recording.write();

        if let Some(ref mut engine) = *recording_guard {
            if engine.is_recording() {
                return Err(crate::error::RecordingError::AlreadyRecording.into());
            }

            engine.start_recording(None, None)?;

            Ok(serde_json::json!({ "success": true }))
        } else {
            Err(crate::error::RecordingError::ObsInitError(
                "Recording engine not initialized".to_string(),
            )
            .into())
        }
    }

    async fn handle_stop_recording(&self) -> Result<serde_json::Value> {
        let mut recording_guard = self.recording.write();

        if let Some(ref mut engine) = *recording_guard {
            let result = engine.stop_recording()?;

            // Save to timeline
            self.timeline.write().add_recording(result.clone())?;

            Ok(serde_json::to_value(result)?)
        } else {
            Err(crate::error::RecordingError::NotRecording.into())
        }
    }

    async fn handle_get_recordings(&self) -> Result<serde_json::Value> {
        let recordings = self.timeline.read().get_all_recordings()?;
        let response = GetRecordingsResponse { recordings };
        Ok(serde_json::to_value(response)?)
    }

    async fn handle_get_recording(&self, id: uuid::Uuid) -> Result<serde_json::Value> {
        let recording = self.timeline.read().get_recording(id)?;
        Ok(serde_json::to_value(recording)?)
    }

    async fn handle_delete_recording(&self, id: uuid::Uuid) -> Result<serde_json::Value> {
        self.timeline.write().delete_recording(id)?;
        Ok(serde_json::json!({ "success": true }))
    }

    async fn handle_get_timeline(&self, recording_id: uuid::Uuid) -> Result<serde_json::Value> {
        let timeline = self.timeline.read().get_timeline(recording_id)?;
        Ok(serde_json::to_value(timeline)?)
    }

    async fn handle_export_timeline(
        &self,
        recording_id: uuid::Uuid,
        format: super::protocol::ExportFormat,
    ) -> Result<serde_json::Value> {
        let timeline = self.timeline.read().get_timeline(recording_id)?;

        let exported = match format {
            super::protocol::ExportFormat::Json => serde_json::to_string_pretty(&timeline)?,
            super::protocol::ExportFormat::Csv => {
                // Convert to CSV format
                let mut csv = String::from("timestamp,event_type,description\n");
                for event in &timeline.events {
                    csv.push_str(&format!(
                        "{},{},{}\n",
                        event.timestamp.to_rfc3339(),
                        event.event_type,
                        event.description,
                    ));
                }
                csv
            }
        };

        Ok(serde_json::json!({
            "data": exported,
            "format": format,
        }))
    }

    async fn handle_shutdown(&self) -> Result<serde_json::Value> {
        info!("Shutdown requested via IPC");
        // Signal shutdown through status
        *self.status.write() = DaemonStatus::ShuttingDown;
        Ok(serde_json::json!({ "success": true }))
    }
}

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

    #[test]
    fn test_handlers_creation() {
        let settings = Arc::new(RwLock::new(Settings::default()));
        let recording = Arc::new(RwLock::new(None));
        let timeline = Arc::new(RwLock::new(TimelineStore::new()));
        let status = Arc::new(RwLock::new(DaemonStatus::Idle));
        let client_connected = Arc::new(RwLock::new(false));

        let _handlers = IpcHandlers::new(settings, recording, timeline, status, client_connected);
    }
}
