
use crate::utils::ffi;
use crate::data::error::{DarraError, FoEErrorCode, Result};
use std::ffi::{CStr, CString};
use std::sync::{Arc, Mutex, OnceLock};

pub struct FoEInstance {
    master_index: u16,
    slave_index: u16,

    pub default_timeout_ms: i32,

    pub default_password: u32,

    last_error: Option<FoEErrorCode>,
}

impl FoEInstance {

    pub fn new(master_index: u16, slave_index: u16) -> Self {
        Self {
            master_index,
            slave_index,
            default_timeout_ms: 5000,
            default_password: 0,
            last_error: None,
        }
    }

    pub fn last_error(&self) -> Option<FoEErrorCode> {
        self.last_error
    }

    pub fn is_supported(&self) -> bool {
        let proto = unsafe { ffi::GetSlaveMailboxProto(self.master_index, self.slave_index) };
        (proto & 0x08) != 0
    }

    pub fn download(
        &mut self,
        filename: &str,
        password: Option<u32>,
        timeout_ms: Option<i32>,
    ) -> Result<Vec<u8>> {

        self.last_error = None;

        let c_filename = CString::new(filename)
            .map_err(|_| DarraError::InvalidParameter("文件名包含空字节".into()))?;

        let pwd = password.unwrap_or(self.default_password);
        let timeout_us = (timeout_ms.unwrap_or(self.default_timeout_ms)) * 1000;

        let mut data_ptr = std::ptr::null_mut();
        let mut file_size: i32 = 0;

        let result = unsafe {
            ffi::FOERead(
                self.master_index,
                self.slave_index,
                c_filename.as_ptr(),
                pwd,
                &mut data_ptr,
                &mut file_size,
                timeout_us,
            )
        };

        if result == 0 || data_ptr.is_null() || file_size <= 0 {
            if !data_ptr.is_null() {
                unsafe { ffi::FreeMemory(data_ptr as *mut std::os::raw::c_void); }
            }

            self.last_error = Some(FoEErrorCode::from_dll_error(file_size));
            return Err(DarraError::FoeFailed(format!(
                "FoE 下载失败: 从站={}, 文件={}",
                self.slave_index, filename
            )));
        }

        let data = unsafe {
            let slice = std::slice::from_raw_parts(data_ptr as *const u8, file_size as usize);
            let vec = slice.to_vec();
            ffi::FreeMemory(data_ptr as *mut std::os::raw::c_void);
            vec
        };

        Ok(data)
    }

    pub fn upload(
        &self,
        filename: &str,
        file_data: &[u8],
        password: Option<u32>,
        timeout_ms: Option<i32>,
    ) -> Result<()> {
        let c_filename = CString::new(filename)
            .map_err(|_| DarraError::InvalidParameter("文件名包含空字节".into()))?;

        if file_data.is_empty() {
            return Err(DarraError::InvalidParameter("文件数据不能为空".into()));
        }

        let pwd = password.unwrap_or(self.default_password);
        let timeout_us = (timeout_ms.unwrap_or(self.default_timeout_ms)) * 1000;

        let result = unsafe {
            ffi::FOEWrite(
                self.master_index,
                self.slave_index,
                c_filename.as_ptr(),
                pwd,
                file_data.as_ptr() as *const std::os::raw::c_void,
                file_data.len() as i32,
                timeout_us,
            )
        };

        if result == 0 {
            return Err(DarraError::FoeFailed(format!(
                "FoE 上传失败: 从站={}, 文件={}",
                self.slave_index, filename
            )));
        }

        Ok(())
    }

    pub fn set_progress_hook(&self, callback: ffi::FoEProgressCallback) -> bool {
        unsafe { ffi::FOESetProgressHook(self.master_index, Some(callback)) != 0 }
    }

    pub fn clear_progress_hook(&self) -> bool {
        unsafe { ffi::FOEClearProgressHook(self.master_index) != 0 }
    }

    pub fn cancel(&self) -> bool {
        unsafe { ffi::FOERequestCancel(self.master_index, self.slave_index) != 0 }
    }

    pub fn clear_cancel(&self) -> bool {
        unsafe { ffi::FOEClearCancel(self.master_index, self.slave_index) != 0 }
    }

    pub fn set_busy_hook(&self, cb: FoEBusyCallback) {
        add_busy_hook(self.master_index, cb);
    }

    pub fn clear_busy_hooks(&self) {
        clear_busy_hooks(self.master_index);
    }

    pub fn estimate_packet_count(file_size: usize, mailbox_size: usize) -> usize {
        if file_size == 0 || mailbox_size == 0 {
            return 0;
        }
        let data_per_packet = if mailbox_size > 6 { mailbox_size - 6 } else { 128 };
        (file_size + data_per_packet - 1) / data_per_packet
    }

    pub fn default_timeout_ms(&self) -> i32 {
        self.default_timeout_ms
    }

    pub fn set_default_timeout_ms(&mut self, ms: i32) {
        self.default_timeout_ms = ms;
    }

    pub fn default_password(&self) -> u32 {
        self.default_password
    }

    pub fn set_default_password(&mut self, pw: u32) {
        self.default_password = pw;
    }

    pub fn download_with_crc(
        &mut self,
        filename: &str,
        password: Option<u32>,
        timeout_ms: Option<i32>,
        enable_crc: bool,
    ) -> Result<Vec<u8>> {
        let c_filename = CString::new(filename)
            .map_err(|_| DarraError::InvalidParameter("文件名包含空字节".into()))?;

        let pwd = password.unwrap_or(self.default_password);
        let timeout_us = (timeout_ms.unwrap_or(self.default_timeout_ms)) * 1000;

        let mut data_ptr = std::ptr::null_mut();
        let mut file_size: i32 = 0;

        let mut options = ffi::FoEOptions {
            enable_crc: if enable_crc { 1 } else { 0 },
            strict_mode: if enable_crc { 1 } else { 0 },
            auto_append_crc: if enable_crc { 1 } else { 0 },
            expected_crc: 0,
            crc_progress_callback: None,
            crc_callback_userdata: std::ptr::null_mut(),
            reserved: [0u32; 8],
        };

        let result = unsafe {
            ffi::FOEReadEx(
                self.master_index,
                self.slave_index,
                c_filename.as_ptr(),
                pwd,
                &mut data_ptr,
                &mut file_size,
                timeout_us,
                &mut options,
            )
        };

        if result == 0 || data_ptr.is_null() || file_size <= 0 {
            if !data_ptr.is_null() {
                unsafe { ffi::FreeMemory(data_ptr as *mut std::os::raw::c_void); }
            }
            self.last_error = Some(FoEErrorCode::NotDefined);
            return Err(DarraError::FoeFailed(format!(
                "FoE CRC下载失败: 从站={}, 文件={}",
                self.slave_index, filename
            )));
        }

        let data = unsafe {
            let slice = std::slice::from_raw_parts(data_ptr as *const u8, file_size as usize);
            let vec = slice.to_vec();
            ffi::FreeMemory(data_ptr as *mut std::os::raw::c_void);
            vec
        };

        self.last_error = None;
        Ok(data)
    }

    pub fn upload_with_crc(
        &mut self,
        filename: &str,
        file_data: &[u8],
        password: Option<u32>,
        timeout_ms: Option<i32>,
        enable_crc: bool,
    ) -> Result<()> {
        let c_filename = CString::new(filename)
            .map_err(|_| DarraError::InvalidParameter("文件名包含空字节".into()))?;

        if file_data.is_empty() {
            return Err(DarraError::InvalidParameter("文件数据不能为空".into()));
        }

        let pwd = password.unwrap_or(self.default_password);
        let timeout_us = (timeout_ms.unwrap_or(self.default_timeout_ms)) * 1000;

        let mut options = ffi::FoEOptions {
            enable_crc: if enable_crc { 1 } else { 0 },
            strict_mode: if enable_crc { 1 } else { 0 },
            auto_append_crc: if enable_crc { 1 } else { 0 },
            expected_crc: 0,
            crc_progress_callback: None,
            crc_callback_userdata: std::ptr::null_mut(),
            reserved: [0u32; 8],
        };

        let result = unsafe {
            ffi::FOEWriteEx(
                self.master_index,
                self.slave_index,
                c_filename.as_ptr(),
                pwd,
                file_data.as_ptr() as *const std::os::raw::c_void,
                file_data.len() as i32,
                timeout_us,
                &mut options,
            )
        };

        if result == 0 {
            self.last_error = Some(FoEErrorCode::NotDefined);
            return Err(DarraError::FoeFailed(format!(
                "FoE CRC上传失败: 从站={}, 文件={}",
                self.slave_index, filename
            )));
        }

        self.last_error = None;
        Ok(())
    }

    pub fn get_error_description(error_code: FoEErrorCode) -> &'static str {
        match error_code {
            FoEErrorCode::NotDefined       => "未定义错误",
            FoEErrorCode::NotFound         => "文件未找到",
            FoEErrorCode::AccessDenied     => "访问被拒绝",
            FoEErrorCode::DiskFull         => "磁盘已满",
            FoEErrorCode::Illegal          => "非法操作",
            FoEErrorCode::PacketNumberWrong => "数据包序号错误",
            FoEErrorCode::AlreadyExists    => "文件已存在",
            FoEErrorCode::NoUser           => "无此用户",
            FoEErrorCode::BootstrapOnly    => "仅 Bootstrap 模式可用",
            FoEErrorCode::NotBootstrap     => "不在 Bootstrap 模式",
            FoEErrorCode::NoRights         => "权限不足",
            FoEErrorCode::ProgramError     => "程序错误",
        }
    }
}

impl FoEInstance {

    pub fn download_blocking(
        master_index: u16,
        slave_index: u16,
        filename: String,
        password: Option<u32>,
        timeout_ms: Option<i32>,
    ) -> std::thread::JoinHandle<Result<Vec<u8>>> {
        std::thread::spawn(move || {
            let mut foe = FoEInstance::new(master_index, slave_index);
            foe.download(&filename, password, timeout_ms)
        })
    }

    pub fn upload_blocking(
        master_index: u16,
        slave_index: u16,
        filename: String,
        file_data: Vec<u8>,
        password: Option<u32>,
        timeout_ms: Option<i32>,
    ) -> std::thread::JoinHandle<Result<()>> {
        std::thread::spawn(move || {
            let foe = FoEInstance::new(master_index, slave_index);
            foe.upload(&filename, &file_data, password, timeout_ms)
        })
    }
}

#[cfg(feature = "async-tokio")]
impl FoEInstance {

    pub async fn download_async(
        &self,
        filename: String,
        password: Option<u32>,
        timeout_ms: Option<i32>,
    ) -> Result<Vec<u8>> {
        let master = self.master_index;
        let slave = self.slave_index;
        let default_pwd = self.default_password;
        let default_to = self.default_timeout_ms;
        tokio::task::spawn_blocking(move || {
            let mut foe = FoEInstance::new(master, slave);
            foe.default_password = default_pwd;
            foe.default_timeout_ms = default_to;
            foe.download(&filename, password, timeout_ms)
        })
        .await
        .map_err(|e| DarraError::Other(format!("tokio join error: {}", e)))?
    }

    pub async fn upload_async(
        &self,
        filename: String,
        file_data: Vec<u8>,
        password: Option<u32>,
        timeout_ms: Option<i32>,
    ) -> Result<()> {
        let master = self.master_index;
        let slave = self.slave_index;
        let default_pwd = self.default_password;
        let default_to = self.default_timeout_ms;
        tokio::task::spawn_blocking(move || {
            let mut foe = FoEInstance::new(master, slave);
            foe.default_password = default_pwd;
            foe.default_timeout_ms = default_to;
            foe.upload(&filename, &file_data, password, timeout_ms)
        })
        .await
        .map_err(|e| DarraError::Other(format!("tokio join error: {}", e)))?
    }
}

impl crate::abstractions::MailboxProtocol for FoEInstance {
    fn protocol_type(&self) -> u8 { 0x04 }
    fn protocol_name(&self) -> &'static str { "FoE" }

    fn is_supported(&self) -> bool {
        FoEInstance::is_supported(self)
    }

    fn last_error_code(&self) -> u32 {
        match self.last_error {
            Some(e) => e as u32,
            None => 0,
        }
    }

    fn statistics(&self) -> crate::abstractions::MailboxStatistics {
        let mut stats = ffi::EcMbxStatsC::default();
        let rc = unsafe {
            ffi::mbx_get_stats_by_master(
                self.master_index, self.slave_index, 0x04, &mut stats,
            )
        };
        if rc == 1 {
            stats.into()
        } else {
            crate::abstractions::MailboxStatistics::empty()
        }
    }

    fn reset_statistics(&self) {
        unsafe {
            ffi::mbx_reset_stats_by_master(self.master_index, self.slave_index, 0x04);
        }
    }
}

#[derive(Debug, Clone)]
pub struct FoEBusyEvent {

    pub slave_index: u16,

    pub done: u16,

    pub entire: u16,

    pub text: String,

    pub retry_idx: i32,

    pub percent: u32,
}

impl FoEBusyEvent {
    fn new(slave: u16, done: u16, entire: u16, text: String, retry_idx: i32) -> Self {
        let percent = if entire > 0 {
            (((done as u64) * 100) / entire as u64).min(100) as u32
        } else {
            0
        };
        Self {
            slave_index: slave,
            done,
            entire,
            text,
            retry_idx,
            percent,
        }
    }
}

pub type FoEBusyCallback = Arc<dyn Fn(&FoEBusyEvent) + Send + Sync>;

fn busy_registry() -> &'static Mutex<std::collections::HashMap<u16, Vec<FoEBusyCallback>>> {
    static REG: OnceLock<Mutex<std::collections::HashMap<u16, Vec<FoEBusyCallback>>>> =
        OnceLock::new();
    REG.get_or_init(|| Mutex::new(std::collections::HashMap::new()))
}

fn busy_bound_masters() -> &'static Mutex<std::collections::HashSet<u16>> {
    static BOUND: OnceLock<Mutex<std::collections::HashSet<u16>>> = OnceLock::new();
    BOUND.get_or_init(|| Mutex::new(std::collections::HashSet::new()))
}

extern "C" fn busy_trampoline(
    slave: u16,
    done: u16,
    entire: u16,
    text: *const std::os::raw::c_char,
    retry_idx: std::os::raw::c_int,
) {
    let text_str = if text.is_null() {
        String::new()
    } else {
        unsafe { CStr::from_ptr(text) }
            .to_string_lossy()
            .into_owned()
    };

    let snapshots: Vec<(u16, Vec<FoEBusyCallback>)> = match busy_registry().lock() {
        Ok(g) => g.iter().map(|(k, v)| (*k, v.clone())).collect(),
        Err(_) => return,
    };

    if snapshots.is_empty() {
        return;
    }

    for (_master, cbs) in &snapshots {
        for cb in cbs {
            let ev = FoEBusyEvent::new(slave, done, entire, text_str.clone(), retry_idx as i32);
            let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| cb(&ev)));
        }
    }
}

fn add_busy_hook(master_index: u16, cb: FoEBusyCallback) {
    if let Ok(mut reg) = busy_registry().lock() {
        reg.entry(master_index).or_default().push(cb);
    }

    let need_bind = match busy_bound_masters().lock() {
        Ok(mut set) => set.insert(master_index),
        Err(_) => false,
    };
    if need_bind {
        unsafe {
            let _ = ffi::FOESetBusyHook(master_index, Some(busy_trampoline));
        }
    }
}

fn clear_busy_hooks(master_index: u16) {
    if let Ok(mut reg) = busy_registry().lock() {
        reg.remove(&master_index);
    }
    let was_bound = match busy_bound_masters().lock() {
        Ok(mut set) => set.remove(&master_index),
        Err(_) => false,
    };
    if was_bound {
        unsafe {
            let _ = ffi::FOESetBusyHook(master_index, None);
        }
    }
}
