use bytes::Bytes;
use futures::TryStreamExt;
use tokio::sync::watch;
use tokio_util::codec::{Framed, LengthDelimitedCodec};

use crate::*;

#[cfg(windows)]
use tokio::net::windows::named_pipe::{ClientOptions, NamedPipeClient};
#[cfg(unix)]
use tokio::net::UnixStream;

#[cfg(unix)]
type PlatformStream = UnixStream;
#[cfg(windows)]
type PlatformStream = NamedPipeClient;

pub struct Client<const COLS: usize, const ROWS: usize> {
    socket_path: String,
    state_sender: watch::Sender<osc::State<COLS, ROWS>>,
    state_receiver: watch::Receiver<osc::State<COLS, ROWS>>,
}

impl<const COLS: usize, const ROWS: usize> Client<COLS, ROWS> {
    pub fn new(socket_path: &str) -> Result<Self> {
        let initial_state = osc::State::new(120.0);
        // Test data
        let initial_state = {
            let mut initial_state = initial_state;
            initial_state.selected_column = if COLS > 2 { 2 } else { 0 };
            initial_state.selected_row = if ROWS > 3 { 3 } else { 0 };

            initial_state.columns[0].tracks[0].state = osc::TrackState::Playing;
            initial_state.columns[0].tracks[0].volume = 0.3;
            initial_state.columns[0].tracks[1].state = osc::TrackState::Idle;
            initial_state.columns[0].tracks[1].volume = 1.0;
            initial_state.columns[1].tracks[0].state = osc::TrackState::Idle;
            initial_state.columns[1].tracks[0].volume = 0.9;
            initial_state.columns[1].tracks[1].state = osc::TrackState::Playing;
            initial_state.columns[1].tracks[1].volume = 0.8;
            initial_state.columns[2].tracks[0].state = osc::TrackState::Idle;
            initial_state.columns[2].tracks[0].volume = 0.7;
            initial_state.columns[2].tracks[3].state = osc::TrackState::Recording;
            initial_state.columns[2].tracks[3].volume = 0.6;

            initial_state
        };
        let (state_sender, state_receiver) = watch::channel(initial_state);

        Ok(Self {
            socket_path: socket_path.to_string(),
            state_sender,
            state_receiver,
        })
    }

    pub fn state_receiver(&self) -> watch::Receiver<osc::State<COLS, ROWS>> {
        self.state_receiver.clone()
    }

    pub async fn run(&self) -> Result<()> {
        loop {
            match self.try_connect_and_run().await {
                Ok(()) => {
                    tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
                }
                Err(e) => {
                    log::error!("OSC connection error: {}", e);
                    tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
                }
            }
        }
    }

    async fn try_connect_and_run(&self) -> Result<()> {
        let stream = self.connect().await?;
        let mut framed = Framed::new(stream, LengthDelimitedCodec::new());

        let mut current_state = self.state_receiver.borrow().clone();

        while let Some(frame) = framed.try_next().await? {
            if let Err(e) = self.handle_osc_data(frame.into(), &mut current_state).await {
                log::error!("Error handling OSC data: {}", e);
            }
        }

        Ok(())
    }

    async fn handle_osc_data(
        &self,
        data: Bytes,
        current_state: &mut osc::State<COLS, ROWS>,
    ) -> Result<()> {
        let (_, osc_packet) = rosc::decoder::decode_udp(&data)
            .map_err(|e| anyhow!("Failed to decode OSC packet: {}", e))?;

        if let Some(update) = osc::Message::from_osc_packet(osc_packet)? {
            current_state.update(&update);

            // Send updated state through watch channel
            if let Err(_) = self.state_sender.send(current_state.clone()) {
                return Err(anyhow!("Failed to send state update"));
            }
        }

        Ok(())
    }

    #[cfg(unix)]
    async fn connect(&self) -> Result<PlatformStream> {
        let stream = UnixStream::connect(&self.socket_path).await.map_err(|e| {
            anyhow!(
                "Failed to connect to Unix socket {}: {}",
                self.socket_path,
                e
            )
        })?;
        Ok(stream)
    }

    #[cfg(windows)]
    async fn connect(&self) -> Result<PlatformStream> {
        let pipe_path = if self.socket_path.starts_with(r"\\.\pipe\") {
            self.socket_path.clone()
        } else {
            format!(r"\\.\pipe\{}", self.socket_path)
        };

        let client = ClientOptions::new()
            .open(&pipe_path)
            .map_err(|e| anyhow!("Failed to connect to named pipe {}: {}", pipe_path, e))?;
        Ok(client)
    }
}
