use std::{
    fs,
    io::{self, Read, Write},
    os::unix::net::{UnixListener, UnixStream},
    sync::mpsc::{Sender, channel},
};

use crate::{
    DaemonError, HandlerRequest,
    daemon_impl::{IpcCommand, IpcError, IpcResponse, unix::SELENE_UNIX_SOCKET_PATH},
};

const MAX_MESSAGE_SIZE: usize = 64 * 1024;

pub(super) fn listen(tx: Sender<HandlerRequest>) -> io::Result<()> {
    fs::remove_file(SELENE_UNIX_SOCKET_PATH)?;
    let listener = UnixListener::bind(SELENE_UNIX_SOCKET_PATH)?;
    println!("Started unix listener for {}", SELENE_UNIX_SOCKET_PATH);

    while let Some(stream) = listener.incoming().next() {
        let stream = match stream {
            Ok(v) => v,
            Err(err) => {
                eprintln!("Stream could not be connected to: {err}");
                continue;
            }
        };

        let tx = tx.clone();
        std::thread::spawn(move || {
            let peer = stream.peer_addr().ok();
            if let Err(err) = packet_deserializer(tx, stream) {
                eprintln!("Client handle for {peer:?} failed with error: {err}")
            };
        });
    }

    Ok(())
}

fn packet_deserializer(
    handler_tx: Sender<HandlerRequest>,
    mut stream: UnixStream,
) -> Result<(), DaemonError> {
    loop {
        // Read message length from the stream
        let mut len_buf = [0u8; 4];
        if let Err(_) = stream.read_exact(&mut len_buf) {
            break;
        };

        let message_len = u32::from_be_bytes(len_buf) as usize;

        // Error if packet is more than 64 KiB
        if message_len > MAX_MESSAGE_SIZE {
            send_response(IpcResponse::Error(IpcError::PacketTooLarge), &mut stream)?;

            let mut remaining = message_len;
            let mut discard = [0u8; 8192];
            while remaining > 0 {
                let read_size = remaining.min(discard.len());
                stream.read_exact(&mut discard[..read_size])?;
                remaining -= read_size;
            }

            continue;
        }

        // Read packet into buf
        let mut buf = vec![0u8; message_len];
        stream.read_exact(&mut buf)?;

        // Deserialize [`IpcCommand`] or return deserialize error
        let Ok(command): Result<IpcCommand, _> = ciborium::from_reader(buf.as_slice()) else {
            send_response(
                IpcResponse::Error(IpcError::FailedToDeserialize),
                &mut stream,
            )?;
            continue;
        };

        println!(
            "Recieved '{command}' from {peer:?}",
            peer = stream.peer_addr()
        );

        let (tx, rx) = channel();
        handler_tx
            .send(HandlerRequest::from_ipc(command, tx))
            .map_err(|_| DaemonError::ChannelFailed)?;
        let response = rx.recv().map_err(|_| DaemonError::ChannelFailed)?;

        // Send command response
        send_response(response.into(), &mut stream)?
    }
    Ok(())
}

fn send_response(response: IpcResponse, stream: &mut UnixStream) -> Result<(), DaemonError> {
    println!("Sending response '{response}' to {:?}", stream.peer_addr());
    let mut response_buf = Vec::new();
    ciborium::into_writer(&response, &mut response_buf)?;

    let response_len = response_buf.len();
    let mut packet = Vec::with_capacity(response_len + 4);

    packet.extend((response_len as u32).to_be_bytes());
    packet.extend(response_buf);
    stream.write_all(&packet)?;
    stream.flush()?;

    Ok(())
}
