use cocoa::base::nil;
use objc::{msg_send, sel, sel_impl};
use screencapturekit_sys::{
    cm_sample_buffer_ref::CMSampleBufferRef,
    content_filter::{UnsafeContentFilter, UnsafeInitParams},
    os_types::rc::Id,
    shareable_content::{UnsafeSCRunningApplication, UnsafeSCShareableContent},
    stream::UnsafeSCStream,
    stream_configuration::UnsafeStreamConfiguration,
    stream_error_handler::UnsafeSCStreamError,
    stream_output_handler::UnsafeSCStreamOutput,
};

use crate::emit_log;
use tauri::{AppHandle, Emitter, Manager}; // Ensure Emitter is verified for v2 (it is trait typically)

pub struct SystemAudioCapture {
    stream: Option<Id<UnsafeSCStream>>,
    sample_rate: u32,
    excluded_apps: Vec<String>,
    app_handle: Option<AppHandle>,
}

struct AudioOutputWrapper {
    callback: Box<dyn Fn(&[f32]) + Send + Sync>,
}

impl UnsafeSCStreamOutput for AudioOutputWrapper {
    fn did_output_sample_buffer(&self, sample: Id<CMSampleBufferRef>, of_type: u8) {
        if of_type == 1 {
            // Audio
            let buffers = sample.get_av_audio_buffer_list();
            for buffer in buffers {
                let data_u8 = buffer.data;
                let data_f32: &[f32] = unsafe {
                    std::slice::from_raw_parts(data_u8.as_ptr() as *const f32, data_u8.len() / 4)
                };

                (self.callback)(data_f32);
            }
        }
    }
}

struct ErrorHandler;
impl UnsafeSCStreamError for ErrorHandler {
    fn handle_error(&self) {
        // eprintln!("Stream Error");
    }
}

pub async fn check_permissions() -> bool {
    UnsafeSCShareableContent::get().is_ok()
}

impl SystemAudioCapture {
    pub fn new(
        sample_rate: u32,
        excluded_apps: Vec<String>,
        app_handle: Option<AppHandle>,
    ) -> Self {
        Self {
            stream: None,
            sample_rate,
            excluded_apps,
            app_handle,
        }
    }

    pub fn get_shareable_content() -> Result<Id<UnsafeSCShareableContent>, String> {
        UnsafeSCShareableContent::get().map_err(|_| format!("Failed to get content"))
    }

    // Internal helper that takes content
    fn build_filter_with_content(
        &self,
        content: Id<UnsafeSCShareableContent>,
    ) -> Result<Id<UnsafeContentFilter>, String> {
        if let Some(app) = &self.app_handle {
            emit_log(app, "TRACE", "[SCK] build_filter_with_content started.");
        }

        let displays = content.displays();
        let display = displays.first().ok_or("No display found")?;

        if let Some(app) = &self.app_handle {
            emit_log(
                app,
                "DEBUG",
                &format!("[SCK] Excluded Apps Config: {:?}", self.excluded_apps),
            );
        }

        if self.excluded_apps.is_empty() {
            if let Some(app) = &self.app_handle {
                emit_log(
                    app,
                    "DEBUG",
                    "[SCK] Excluded list empty. Capturing entire display.",
                );
            }
            return Ok(UnsafeContentFilter::init(UnsafeInitParams::Display(
                display.clone(),
            )));
        }

        let mut apps_to_exclude = Vec::new();
        let all_apps = content.applications();

        if let Some(app) = &self.app_handle {
            emit_log(
                app,
                "DEBUG",
                &format!("[SCK] Found {} running applications.", all_apps.len()),
            );
        }

        // Prepare lowercase excluded list for case-insensitive matching
        let excluded_lower: Vec<String> = self
            .excluded_apps
            .iter()
            .map(|s| s.to_lowercase())
            .collect();

        // Known 3CX PWA Bundle-ID suffix (Chrome PWA).
        // The full ID is "com.google.Chrome.app.dmmmfdbaapefjmjeomigbhpmjbbjgnph"
        // which does NOT contain "3cx", so we must match by PWA ID.
        const KNOWN_3CX_PWA_ID: &str = "dmmmfdbaapefjmjeomigbhpmjbbjgnph";

        // First pass: Check if 3CX PWA is running.
        // If it is, we must also protect the Chrome main process from exclusion,
        // because ScreenCaptureKit routes PWA audio through the Chrome host process.
        let threecx_pwa_running = all_apps.iter().any(|a| {
            a.get_bundle_identifier()
                .map(|bid| {
                    let bl = bid.to_lowercase();
                    bl.contains("3cx") || bl.contains(KNOWN_3CX_PWA_ID)
                })
                .unwrap_or(false)
        });

        if threecx_pwa_running {
            if let Some(h) = &self.app_handle {
                emit_log(
                    h,
                    "INFO",
                    "[SCK] 3CX detected as running. Chrome main process will be protected from exclusion.",
                );
            }
        }

        for app in all_apps {
            if let Some(bid) = app.get_bundle_identifier() {
                let bid_lower = bid.to_lowercase();

                // --- SAFETY CHECKS (ALLOWLIST) ---

                // 1. ALWAYS Allow 3CX (native app OR known PWA Bundle-ID)
                if bid_lower.contains("3cx") || bid_lower.contains(KNOWN_3CX_PWA_ID) {
                    if let Some(h) = &self.app_handle {
                        emit_log(h, "INFO", &format!("[SCK] ALLOWLIST: Found 3CX App ('{}'). Ensuring it is NOT excluded.", bid));
                    }
                    continue;
                }

                // 2. PROTECT Chrome main process when 3CX PWA is running.
                // ScreenCaptureKit routes Chrome PWA audio through the main Chrome process.
                // If we exclude Chrome while 3CX PWA is active, 3CX audio is lost.
                if threecx_pwa_running && bid_lower == "com.google.chrome" {
                    if let Some(h) = &self.app_handle {
                        emit_log(
                            h,
                            "INFO",
                            "[SCK] ALLOWLIST: Preserving Chrome main process because 3CX PWA is running.",
                        );
                    }
                    continue;
                }

                // 3. PROTECT Chrome PWAs (prevent wildcard matching from "Google Chrome")
                if bid_lower.contains("chrome.app") {
                    // Only exclude if the user has explicitly blocked THIS EXACT app ID.
                    if excluded_lower.contains(&bid_lower) {
                        if let Some(h) = &self.app_handle {
                            emit_log(
                                h,
                                "DEBUG",
                                &format!("[SCK] Excluding PWA App (Exact Match): '{}'", bid),
                            );
                        }
                        apps_to_exclude.push(app);
                        continue;
                    } else {
                        // It's a PWA, and not exactly blocked.
                        // But if "Google Chrome" is blocked, `starts_with` would kill this.
                        // So we SKIP the rest of the loop to protect it.
                        if let Some(h) = &self.app_handle {
                            emit_log(h, "INFO", &format!("[SCK] ALLOWLIST: Preserving Chrome PWA ('{}') from wildcard exclusion.", bid));
                        }
                        continue;
                    }
                }

                // --- STANDARD EXCLUSION LOGIC ---
                if let Some(matched) = excluded_lower
                    .iter()
                    .find(|excluded| bid_lower.starts_with(*excluded))
                {
                    if let Some(h) = &self.app_handle {
                        emit_log(
                            h,
                            "DEBUG",
                            &format!("[SCK] Excluding App: '{}' (Matches: '{}')", bid, matched),
                        );
                    }
                    apps_to_exclude.push(app);
                } else {
                    // Optional: Log what is KEPT (verbose)
                }
            }
        }

        // NOTE: In screencapturekit-sys 0.2.8, DisplayIncludingApplicationsExceptingWindows
        // actually calls initWithDisplay:excludingApplications: (it's a bug in the crate)
        let filter_init = UnsafeInitParams::DisplayIncludingApplicationsExceptingWindows(
            display.clone(),
            apps_to_exclude,
            Vec::new(),
        );

        Ok(UnsafeContentFilter::init(filter_init))
    }

    async fn build_filter(&self) -> Result<Id<UnsafeContentFilter>, String> {
        let content = Self::get_shareable_content()?;
        self.build_filter_with_content(content)
    }

    pub async fn start<F>(&mut self, callback: F) -> Result<(), String>
    where
        F: Fn(&[f32]) + Send + Sync + 'static,
    {
        let filter = self.build_filter().await?;

        let mut config = UnsafeStreamConfiguration::default();
        config.width = 100;
        config.height = 100;
        config.captures_audio = 1;
        config.sample_rate = self.sample_rate;
        config.channel_count = 2;
        config.excludes_current_process_audio = 1;

        let output_wrapper = AudioOutputWrapper {
            callback: Box::new(callback),
        };

        let stream = UnsafeSCStream::init(filter, config.into(), ErrorHandler);
        stream.add_stream_output(output_wrapper, 1); // 1 = Audio

        stream
            .start_capture()
            .map_err(|_| "Failed to start capture".to_string())?;

        self.stream = Some(stream);
        Ok(())
    }

    pub fn stop(&mut self) {
        if let Some(stream) = &self.stream {
            let _ = stream.stop_capture();
        }
        self.stream = None;
    }

    pub async fn refresh_filter(&mut self) -> Result<(), String> {
        // Fallback to internal fetch
        let content = Self::get_shareable_content()?;
        self.refresh_with_content(content).await
    }

    pub async fn refresh_with_content(
        &mut self,
        content: Id<UnsafeSCShareableContent>,
    ) -> Result<(), String> {
        let stream = match &self.stream {
            Some(s) => s,
            None => return Ok(()),
        };

        let filter = self.build_filter_with_content(content)?;

        // Call updateContentFilter:completionHandler:
        // screencapturekit-sys 0.2.8 does not have this method exposed yet in UnsafeSCStream.
        // We use msg_send! to call it directly on the underlying object.
        unsafe {
            // Get raw pointer to the underlying Objective-C objects.
            // Since we don't have easy access to the inner pointer of Id<T> via methods,
            // we cast the pointer to the Id wrapper itself to a pointer to a pointer.
            // This assumes Id<T> is a transparent wrapper around a pointer.
            let stream_ptr =
                *(stream as *const Id<UnsafeSCStream> as *const *mut objc::runtime::Object);
            let filter_ptr =
                *(&filter as *const Id<UnsafeContentFilter> as *const *mut objc::runtime::Object);

            let _: () =
                msg_send![stream_ptr, updateContentFilter: filter_ptr completionHandler: nil];
        }

        Ok(())
    }
}
