use crate::slip;
use log::{debug, info, trace};
use serialport::SerialPort;
use std::io::Read;
use std::thread;
use std::time::Duration;
use thiserror::Error;

const CMD_SYNC: u8 = 0x08;
const CMD_FLASH_BEGIN: u8 = 0x02;
const CMD_FLASH_DATA: u8 = 0x03;
const CMD_FLASH_END: u8 = 0x04;

/// Specific error states for the ESP8266 bootloader connection and flashing process.
#[derive(Error, Debug)]
pub enum BootloaderError {
    #[error("Serial port error: {0}")]
    Serial(#[from] serialport::Error),

    #[error("IO error: {0}")]
    IO(#[from] std::io::Error),

    #[error("ESP8266 did not respond to the handshake sync packet")]
    HandshakeTimeout,

    #[error("No valid response received for FLASH_BEGIN initialization")]
    FlashInitFailed,

    #[error("Timeout or error writing block {0}")]
    BlockWriteTimeout(u32),

    #[error("No valid acknowledgement received for block {0}")]
    BlockAckFailed(u32),
}

/// The core bootloader controller encapsulating the connection state to the ESP8266.
pub struct EspBootloader {
    port: Box<dyn SerialPort>,
}

impl EspBootloader {
    /// Creates a new instance, opens the targeted serial port, and runs the handshake sequence.
    /// This establishes the physical connection and synchronizes with the ESP8266 bootloader.
    pub fn connect(port_name: &str, baudrate: u32) -> Result<Self, BootloaderError> {
        debug!("Opening port {} at {} baud...", port_name, baudrate);
        let mut port = serialport::new(port_name, baudrate)
            .timeout(Duration::from_millis(200))
            .open()?;

        debug!("Triggering hardware reset into bootloader mode...");
        port.write_data_terminal_ready(false)?;
        port.write_request_to_send(true)?;
        thread::sleep(Duration::from_millis(100));

        port.write_data_terminal_ready(true)?;
        port.write_request_to_send(false)?;
        thread::sleep(Duration::from_millis(50));

        let mut sync_payload = vec![0x07, 0x07, 0x12, 0x20];
        sync_payload.extend(vec![0x55; 32]);

        // Create a temporary instance to access internal methods
        let mut bootloader = EspBootloader { port };

        for i in 1..=5 {
            debug!("Sending sync packet (Attempt {}/5)...", i);
            bootloader.send_command(CMD_SYNC, &sync_payload)?;

            thread::sleep(Duration::from_millis(100));

            let mut read_buffer = vec![0u8; 1024];
            if let Ok(bytes_read) = bootloader.port.read(&mut read_buffer)
                && bytes_read > 0
            {
                let responses = slip::parse_responses(&read_buffer[..bytes_read]);
                if !responses.is_empty() {
                    info!("ESP8266 responded successfully. Connection established.");
                    return Ok(bootloader);
                }
            }
        }

        Err(BootloaderError::HandshakeTimeout)
    }

    /// Prepares the flash memory of the chip by erasing the targeted memory sectors.
    pub fn prepare_flash(
        &mut self,
        total_size: u32,
        block_size: u32,
        start_address: u32,
    ) -> Result<(), BootloaderError> {
        let num_blocks = total_size.div_ceil(block_size);

        info!(
            "Initializing flash: {} bytes in {} blocks to address 0x{:05X}...",
            total_size, num_blocks, start_address
        );

        let mut payload = Vec::with_capacity(16);
        payload.extend_from_slice(&total_size.to_le_bytes());
        payload.extend_from_slice(&num_blocks.to_le_bytes());
        payload.extend_from_slice(&block_size.to_le_bytes());
        payload.extend_from_slice(&start_address.to_le_bytes());

        self.send_command(CMD_FLASH_BEGIN, &payload)?;

        thread::sleep(Duration::from_millis(500));

        let mut read_buffer = vec![0u8; 1024];
        match self.port.read(&mut read_buffer) {
            Ok(bytes_read) => {
                if bytes_read > 0 {
                    let responses = slip::parse_responses(&read_buffer[..bytes_read]);
                    if !responses.is_empty() {
                        debug!("Flash readiness confirmed by ESP8266. Sectors erased.");
                        return Ok(());
                    }
                }
            }
            Err(_) => {
                return Err(BootloaderError::FlashInitFailed);
            }
        }

        Err(BootloaderError::FlashInitFailed)
    }

    /// Transmits a single block of firmware data to the chip's internal flash storage.
    pub fn write_flash_block(
        &mut self,
        seq_number: u32,
        block_data: &[u8],
    ) -> Result<(), BootloaderError> {
        let mut payload = Vec::with_capacity(16 + block_data.len());
        payload.extend_from_slice(&(block_data.len() as u32).to_le_bytes());
        payload.extend_from_slice(&seq_number.to_le_bytes());
        payload.extend_from_slice(&[0, 0, 0, 0]);
        payload.extend_from_slice(&[0, 0, 0, 0]);
        payload.extend_from_slice(block_data);

        self.send_command(CMD_FLASH_DATA, &payload)?;

        let mut read_buffer = vec![0u8; 1024];
        match self.port.read(&mut read_buffer) {
            Ok(bytes_read) => {
                if bytes_read > 0 {
                    let responses = slip::parse_responses(&read_buffer[..bytes_read]);
                    if !responses.is_empty() {
                        trace!("Block {} write acknowledged.", seq_number);
                        return Ok(());
                    }
                }
            }
            Err(_) => {
                return Err(BootloaderError::BlockWriteTimeout(seq_number));
            }
        }

        Err(BootloaderError::BlockAckFailed(seq_number))
    }

    /// Finalizes the flashing process and requests a soft hardware reset to execute the new firmware.
    pub fn finish_flash(&mut self) -> Result<(), BootloaderError> {
        debug!("Wrapping up flash process and requesting ESP8266 reboot...");
        let payload = vec![0x01, 0x00, 0x00, 0x00];
        self.send_command(CMD_FLASH_END, &payload)?;

        let mut read_buffer = vec![0u8; 1024];
        let _ = self.port.read(&mut read_buffer);
        Ok(())
    }

    /// Internal helper to assemble, checksum, and transmit raw SLIP-encoded frames.
    fn send_command(&mut self, cmd: u8, payload: &[u8]) -> Result<(), BootloaderError> {
        let mut frame = Vec::with_capacity(8 + payload.len());
        frame.push(0x00);
        frame.push(cmd);
        let size = payload.len() as u16;
        frame.extend_from_slice(&size.to_le_bytes());
        let checksum = self.calculate_checksum(payload);
        frame.extend_from_slice(&checksum.to_le_bytes());
        frame.extend_from_slice(payload);

        let slip_packet = slip::encode_packet(&frame);
        trace!("Sending raw SLIP command frame: {:02X?}", slip_packet);

        self.port.write_all(&slip_packet)?;
        self.port.flush()?;
        Ok(())
    }

    /// Generates the standard ESP8266 custom cumulative XOR checksum.
    fn calculate_checksum(&self, payload: &[u8]) -> u32 {
        let mut checksum = 0xEFEF;
        for &byte in payload {
            checksum ^= byte as u32;
        }
        checksum
    }
}

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

    #[test]
    fn test_calculate_checksum_empty() {
        let bl = EspBootloader {
            // Instantiate with a safe system fallback handle for cross-platform test compliance
            port: serialport::new("/dev/null", 115200)
                .open()
                .unwrap_or_else(|_| {
                    serialport::new("COM1", 115200)
                        .open()
                        .unwrap_or(Box::new(MockPort))
                }),
        };
        // An empty payload must exactly return the initialization vector 0xEFEF
        assert_eq!(bl.calculate_checksum(&[]), 0xEFEF);
    }

    #[test]
    fn test_calculate_checksum_accumulation() {
        let bl = EspBootloader {
            port: Box::new(MockPort),
        };
        let payload = [0x01, 0x02];

        // Manual verification chain:
        // 0xEFEF ^ 0x01 = 0xFFEE
        // 0xFFEE ^ 0x02 = 0xFFEC
        assert_eq!(bl.calculate_checksum(&payload), 0xEFEF ^ 0x01 ^ 0x02);
    }

    // Minimal structural stub allowing struct instantiation without an active OS serial interface during tests
    struct MockPort;
    impl std::io::Read for MockPort {
        fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
            Ok(0)
        }
    }
    impl std::io::Write for MockPort {
        fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
            Ok(buf.len())
        }
        fn flush(&mut self) -> std::io::Result<()> {
            Ok(())
        }
    }
    impl SerialPort for MockPort {
        fn name(&self) -> Option<String> {
            None
        }
        fn baud_rate(&self) -> serialport::Result<u32> {
            Ok(115200)
        }
        fn data_bits(&self) -> serialport::Result<serialport::DataBits> {
            Ok(serialport::DataBits::Eight)
        }
        fn flow_control(&self) -> serialport::Result<serialport::FlowControl> {
            Ok(serialport::FlowControl::None)
        }
        fn parity(&self) -> serialport::Result<serialport::Parity> {
            Ok(serialport::Parity::None)
        }
        fn stop_bits(&self) -> serialport::Result<serialport::StopBits> {
            Ok(serialport::StopBits::One)
        }
        fn timeout(&self) -> Duration {
            Duration::from_millis(100)
        }
        fn set_baud_rate(&mut self, _b: u32) -> serialport::Result<()> {
            Ok(())
        }
        fn set_data_bits(&mut self, _d: serialport::DataBits) -> serialport::Result<()> {
            Ok(())
        }
        fn set_flow_control(&mut self, _f: serialport::FlowControl) -> serialport::Result<()> {
            Ok(())
        }
        fn set_parity(&mut self, _p: serialport::Parity) -> serialport::Result<()> {
            Ok(())
        }
        fn set_stop_bits(&mut self, _s: serialport::StopBits) -> serialport::Result<()> {
            Ok(())
        }
        fn set_timeout(&mut self, _t: Duration) -> serialport::Result<()> {
            Ok(())
        }
        fn write_request_to_send(&mut self, _l: bool) -> serialport::Result<()> {
            Ok(())
        }
        fn write_data_terminal_ready(&mut self, _l: bool) -> serialport::Result<()> {
            Ok(())
        }
        fn read_clear_to_send(&mut self) -> serialport::Result<bool> {
            Ok(true)
        }
        fn read_data_set_ready(&mut self) -> serialport::Result<bool> {
            Ok(true)
        }
        fn read_ring_indicator(&mut self) -> serialport::Result<bool> {
            Ok(false)
        }
        fn read_carrier_detect(&mut self) -> serialport::Result<bool> {
            Ok(true)
        }
        fn bytes_to_read(&self) -> serialport::Result<u32> {
            Ok(0)
        }
        fn bytes_to_write(&self) -> serialport::Result<u32> {
            Ok(0)
        }
        fn clear(&self, _b: serialport::ClearBuffer) -> serialport::Result<()> {
            Ok(())
        }
        fn try_clone(&self) -> serialport::Result<Box<dyn SerialPort>> {
            Err(serialport::Error::new(
                serialport::ErrorKind::NoDevice,
                "Mock",
            ))
        }
        fn set_break(&self) -> serialport::Result<()> {
            Ok(())
        }
        fn clear_break(&self) -> serialport::Result<()> {
            Ok(())
        }
    }
}
