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

use lunar_lib::{database::DbHandle, debug, error, iterator_ext::IteratorExtensions, trace, warn};
use selene_core::database::LibraryDb;
use serde::Serialize;

use crate::{
    SHUTDOWN,
    client::{IpcCommand, PacketError, PacketType},
    config::{DaemonConfig, daemon_config},
    listener::IpcListener,
    player::{PlayerCommand, PlayerError, PlayerEvent, PlayerRequest, PlayerTx},
    playlist::Playable,
    wait,
};

const MAX_MESSAGE_SIZE: usize = 64 * 1024 * 1024;
/// Location of the socket file
pub(crate) static SELENE_UNIX_SOCKET_PATH: OnceLock<PathBuf> = OnceLock::new();
pub fn selene_unix_socket_path() -> &'static Path {
    SELENE_UNIX_SOCKET_PATH
        .get()
        .expect("Socket path should be set on daemon start")
}

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

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

        let _ = SELENE_UNIX_SOCKET_PATH.set(daemon_config().main.socket_path.clone());
        let socket_dir = selene_unix_socket_path().parent().unwrap();

        if socket_dir.exists() {
            if socket_dir.is_dir() {
                fs::remove_dir_all(socket_dir)?;
            } else {
                fs::remove_file(socket_dir)?;
            }
        }

        fs::create_dir_all(selene_unix_socket_path().parent().unwrap())?;

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

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

        Ok(tx)
    }

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

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

        loop {
            if SHUTDOWN.load(Ordering::Relaxed) {
                clients.retain(|client| client.send(PlayerEvent::Shutdown).is_ok());
                return Ok(());
            }

            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),
            }

            wait();
        }
    }
}

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

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

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

        let client = Self {
            rx,
            player_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<(), Box<dyn std::error::Error>> {
        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 MAX_MESSAGE_SIZE
            if message_len > MAX_MESSAGE_SIZE {
                self.send_error(PacketError::PacketTooLarge {
                    size: message_len,
                    max_size: 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 = postcard::from_bytes(buf.as_slice())?;

            self.run_ipc(command)?;

            wait();
        }
    }

    fn send_packet<D: Serialize>(&mut self, packet_type: PacketType, data: &D) -> io::Result<()> {
        let buf = postcard::to_stdvec(data).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<T: Serialize>(&mut self, response: &T) -> io::Result<()> {
        trace!("Sending response to {:?}", self.addr,);
        self.send_packet(PacketType::Response, response)
    }

    fn send_error(&mut self, error: PacketError) -> io::Result<()> {
        self.send_packet(PacketType::Error, &error)
    }

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

    fn send_disconnect(&mut self) -> io::Result<()> {
        self.stream.write_all(&[PacketType::Disconnect as u8])
    }
}

impl UnixClientHandler {
    fn run_ipc(&mut self, command: IpcCommand) -> Result<(), Box<dyn std::error::Error>> {
        match command {
            IpcCommand::Flush => {
                let (tx, rx) = channel();
                self.player_tx
                    .command(PlayerCommand::Flush { callback: tx })?;
                rx.recv()?;
                self.send_response(&())?;
            }
            IpcCommand::Disconnect => {
                let _ = self.send_disconnect();
            }
            IpcCommand::ReloadConfig => {
                let result = DaemonConfig::reload().map_err(|err| err.to_string());
                self.send_response(&result)?;
            }
            IpcCommand::Play { collectables } => {
                let db = DbHandle::<LibraryDb>::open()?;

                let playables = collectables
                    .into_iter()
                    .try_map(|c| Playable::from_collectable(c, &db))?
                    .to_vec();

                self.player_tx.command(PlayerCommand::Play { playables })?;
            }
            IpcCommand::Stop => {
                self.player_tx.command(PlayerCommand::Stop)?;
            }
            IpcCommand::SetIsPlaying { is_playing } => {
                self.player_tx
                    .command(PlayerCommand::SetIsPlaying { is_playing })?;
            }
            IpcCommand::TogglePlaying => {
                let (tx, rx) = channel();
                self.player_tx
                    .command(PlayerCommand::TogglePlaying { callback: Some(tx) })?;
                let response = rx.recv()?;
                self.send_response(&response)?;
            }
            IpcCommand::Seek { seconds, increment } => {
                let (tx, rx) = channel();
                self.player_tx.command(PlayerCommand::Seek {
                    seconds,
                    increment,
                    callback: Some(tx),
                })?;
                let response = rx.recv()?;
                self.send_response(&response)?;
            }
            IpcCommand::SetVolume { volume, increment } => {
                let (tx, rx) = channel();
                self.player_tx.command(PlayerCommand::SetVolume {
                    volume,
                    increment,
                    callback: Some(tx),
                })?;
                let response = rx.recv()?;
                self.send_response(&response)?;
            }
            IpcCommand::QueueSet {
                tracks,
                expected_state,
            } => {
                let db = DbHandle::<LibraryDb>::open()?;
                let playables = tracks
                    .into_iter()
                    .try_map(|p| Playable::from_collectable(p, &db))?
                    .collect();

                let (tx, rx) = channel();
                self.player_tx.command(PlayerCommand::QueueSet {
                    to: playables,
                    expected_state,
                    callback: Some(tx),
                })?;
                let response = rx.recv()?;
                self.send_response(&response)?;
            }
            IpcCommand::QueueExtend(collectables) => {
                let db = DbHandle::<LibraryDb>::open()?;
                let playables = collectables
                    .into_iter()
                    .try_map(|p| Playable::from_collectable(p, &db))?
                    .to_vec();

                self.player_tx
                    .command(PlayerCommand::QueueExtend { with: playables })?;
            }
            IpcCommand::QueueShuffle => {
                self.player_tx.command(PlayerCommand::QueueShuffle)?;
            }
            IpcCommand::QueueClear => {
                self.player_tx.command(PlayerCommand::QueueClear)?;
            }
            IpcCommand::PlaylistSet {
                collectables,
                expected_state,
            } => {
                let db = DbHandle::<LibraryDb>::open()?;
                let playables = collectables
                    .into_iter()
                    .try_map(|p| Playable::from_collectable(p, &db))?
                    .collect();

                let (tx, rx) = channel();
                self.player_tx.command(PlayerCommand::PlaylistSet {
                    playables,
                    expected_state,
                    callback: Some(tx),
                })?;
                let response = rx.recv()?;
                self.send_response(&response)?;
            }
            IpcCommand::PlaylistExtend(collectables) => {
                let db = DbHandle::<LibraryDb>::open()?;
                let playables = collectables
                    .into_iter()
                    .try_map(|p| Playable::from_collectable(p, &db))?
                    .collect();

                self.player_tx
                    .command(PlayerCommand::PlaylistExtend { items: playables })?;
            }
            IpcCommand::PlaylistClear => {
                self.player_tx.command(PlayerCommand::PlaylistClear)?;
            }
            IpcCommand::PlaylistSetShuffleMode { shuffle_mode } => {
                self.player_tx
                    .command(PlayerCommand::PlaylistSetShuffleMode { shuffle_mode })?;
            }
            IpcCommand::PlaylistSetLoopMode { loop_mode } => {
                self.player_tx
                    .command(PlayerCommand::PlaylistSetLoopMode { loop_mode })?;
            }
            IpcCommand::TracklistSeek { index, increment } => {
                let (tx, rx) = channel();
                self.player_tx.command(PlayerCommand::TracklistSeek {
                    index,
                    increment,
                    callback: Some(tx),
                })?;
                let response = rx.recv()?;
                self.send_response(&response)?;
            }
            IpcCommand::Next => {
                self.player_tx.command(PlayerCommand::Next)?;
            }
            IpcCommand::Previous => {
                self.player_tx.command(PlayerCommand::Previous)?;
            }
            IpcCommand::GetState { flags } => {
                let (tx, rx) = channel();
                self.player_tx.command(PlayerCommand::GetState {
                    flags,
                    callback: tx,
                })?;
                let response = rx.recv()?;
                self.send_response(&response)?;
            }
        }
        Ok(())
    }
}
