use std::{
    fs,
    io::{self, Read, Write},
    os::unix::net::{SocketAddr, UnixListener, UnixStream},
    path::PathBuf,
    sync::mpsc::{Receiver, Sender, TryRecvError, channel},
    thread,
};

use lunar_lib::{debug, error, trace, warn};
use serde::{Serialize, de::DeserializeOwned};
use thiserror::Error;

use crate::{
    IpcActionError, IpcCommand, PacketType, PlayerEvent, PlayerResponse,
    daemon::{DaemonError, unix_socket_handle::SELENE_UNIX_SOCKET_PATH},
    listener::IpcListener,
    player::{PlayerCommand, PlayerRequest, PlayerTx},
    wait,
};

const MAX_MESSAGE_SIZE: usize = 64 * 1024 * 1024;

pub struct UnixSocketListener {
    rx: Receiver<PlayerEvent>,
    engine_tx: Sender<PlayerRequest>,
    listener: UnixListener,
    socket: PathBuf,
}

#[derive(Debug, Error)]
pub enum ClientHandlerError {
    #[error("Client disconnected")]
    Disconnected,

    #[error("Unexpected io error: {0}")]
    Io(io::Error),

    #[error("Failed to deserialize command: {0}")]
    Read(#[from] ciborium::de::Error<io::Error>),
}

impl From<io::Error> for ClientHandlerError {
    fn from(value: io::Error) -> Self {
        match value.kind() {
            io::ErrorKind::BrokenPipe => Self::Disconnected,
            io::ErrorKind::UnexpectedEof => Self::Disconnected,
            _ => Self::Io(value),
        }
    }
}

impl IpcListener for UnixSocketListener {
    fn open(engine_tx: Sender<PlayerRequest>) -> Result<Sender<PlayerEvent>, DaemonError> {
        let (tx, rx) = channel();

        match fs::remove_file(SELENE_UNIX_SOCKET_PATH.clone()) {
            Ok(()) => (),
            Err(err) if matches!(err.kind(), io::ErrorKind::NotFound) => (),
            Err(err) => return Err(err.into()),
        };

        let socket_listener = Self {
            rx,
            engine_tx: engine_tx.clone(),
            listener: UnixListener::bind(SELENE_UNIX_SOCKET_PATH.clone())?,
            socket: SELENE_UNIX_SOCKET_PATH.clone(),
        };

        thread::spawn(move || {
            if let Err(err) = socket_listener.run() {
                error!("IpcListener failed with error: {err}");
            }
        });

        Ok(tx)
    }

    fn run(self) -> Result<(), DaemonError> {
        self.listener.set_nonblocking(true)?;
        debug!(
            "'UnixSocketListener' started for {socket}",
            socket = self.socket.display()
        );

        let mut clients: Vec<Sender<PlayerEvent>> = Vec::new();

        loop {
            match self.rx.try_recv() {
                Ok(event) => {
                    clients.retain(|client| client.send(event.clone()).is_ok());

                    if matches!(event, PlayerEvent::Shutdown) {
                        return Ok(());
                    }
                }
                Err(TryRecvError::Empty) => (),
                Err(TryRecvError::Disconnected) => {
                    return Ok(());
                }
            }

            match self.listener.accept() {
                Ok((stream, addr)) => {
                    let client_tx = UnixClientHandler::open(self.engine_tx.clone(), stream, addr);
                    clients.push(client_tx);
                }
                Err(err) if matches!(err.kind(), std::io::ErrorKind::WouldBlock) => (),
                Err(err) => return Err(err.into()),
            }

            wait();
        }
    }
}

impl Drop for UnixSocketListener {
    fn drop(&mut self) {
        if let Err(err) = fs::remove_file(SELENE_UNIX_SOCKET_PATH.clone()) {
            error!("Failed to close socket: {err}")
        };
    }
}

pub struct UnixClientHandler {
    rx: Receiver<PlayerEvent>,
    engine_tx: Sender<PlayerRequest>,
    stream: UnixStream,
    addr: SocketAddr,
}

impl UnixClientHandler {
    pub fn open(
        engine_tx: Sender<PlayerRequest>,
        stream: UnixStream,
        addr: SocketAddr,
    ) -> Sender<PlayerEvent> {
        let (tx, rx) = channel();

        let client = Self {
            rx,
            engine_tx,
            stream,
            addr,
        };

        thread::spawn(move || {
            if let Err(err) = client.connect() {
                error!("IPC Client disconnected with error: {err}");
            } else {
                warn!("IPC Client disconnected")
            }
        });

        tx
    }

    fn connect(mut self) -> Result<(), ClientHandlerError> {
        loop {
            loop {
                match self.rx.try_recv() {
                    Ok(event) => {
                        self.send_event(event.clone())?;
                        if matches!(event, PlayerEvent::Shutdown) {
                            return Ok(());
                        }
                    }
                    Err(TryRecvError::Empty) => break,
                    Err(TryRecvError::Disconnected) => return Ok(()),
                }
            }

            self.stream.set_nonblocking(true)?;

            // Read message length from the stream
            let mut len_buf = [0u8; 4];
            match self.stream.read_exact(&mut len_buf[..1]) {
                // Packet read, there is data
                Ok(()) => (),
                // No packet, continue
                Err(err) if matches!(err.kind(), io::ErrorKind::WouldBlock) => {
                    wait();
                    continue;
                }
                Err(err) => return Err(err.into()),
            }

            self.stream.set_nonblocking(false)?;

            // Read the remaining length bytes
            self.stream.read_exact(&mut len_buf[1..])?;
            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 {
                self.send_response(Err(IpcActionError::PacketTooLarge {
                    packet: message_len,
                    max: MAX_MESSAGE_SIZE,
                }))?;

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

                wait();
                continue;
            }

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

            // Deserialize [`IpcCommand`] or return deserialize error
            let command: IpcCommand = ciborium::from_reader(buf.as_slice())?;

            match command {
                // Safe disconnect
                IpcCommand::Disconnect => {
                    let _ = self.send_response(Ok(PlayerResponse::Done));
                    return Ok(());
                }
                // Other command, run it and get the response
                command => {
                    let response = self
                        .engine_tx
                        .request_ipc(PlayerCommand::from_ipc(command).unwrap());

                    if let Err(IpcActionError::Disconnected) = response {
                        let _ = self.send_event(PlayerEvent::Shutdown);
                    }

                    self.send_response(response)?;
                }
            }

            wait();
        }
    }

    fn send_packet<D: Serialize + DeserializeOwned>(
        &mut self,
        packet_type: PacketType,
        data: &D,
    ) -> io::Result<()> {
        let mut buf = Vec::new();
        ciborium::into_writer(data, &mut buf).unwrap();

        self.stream.write_all(&[packet_type as u8])?;
        self.stream.write_all(&(buf.len() as u32).to_be_bytes())?;
        self.stream.write_all(&buf)?;

        Ok(())
    }

    fn send_response(
        &mut self,
        response: Result<PlayerResponse, IpcActionError>,
    ) -> io::Result<()> {
        trace!(
            "Sending response '{display}' to {:?}",
            self.addr,
            display = response
                .as_ref()
                .map(PlayerResponse::to_string)
                .unwrap_or_else(IpcActionError::to_string),
        );
        self.send_packet(PacketType::PlayerResponse, &response)
    }

    fn send_event(&mut self, event: PlayerEvent) -> io::Result<()> {
        trace!("Sending event '{event}' to {:?}", self.addr);
        self.send_packet(PacketType::PlayerEvent, &event)
    }
}
