use interprocess::os::windows::named_pipe::{pipe_mode, tokio::*, PipeListenerOptions};
use std::path::Path;
use std::time::Duration;
use thiserror::Error;
use tokio::sync::broadcast::{Receiver, Sender};
use tokio::time::sleep;
use tokio::{
    io::{AsyncReadExt, AsyncWriteExt},
    select,
};

use crate::models::AuthorizationResponse;
use crate::state_machine::Event;

#[derive(Debug, Error)]
pub enum PipeError {
    #[error(transparent)]
    Io(#[from] std::io::Error),
    #[error(transparent)]
    Unknown(#[from] anyhow::Error),
}

pub struct Pipe {
    quit_signal: Receiver<Event>,
}

const PIPE_NAME: &str = "AvamICP";

impl Pipe {
    pub fn new(quit_signal: Receiver<Event>) -> Self {
        Self { quit_signal }
    }

    pub async fn send(msg: &str) -> Result<(), PipeError> {
        let conn = DuplexPipeStream::<pipe_mode::Bytes>::connect_by_path(format!(
            r"\\.\pipe\{}",
            PIPE_NAME
        ))
        .await?;
        let (_, mut sender) = conn.split();

        sender.write_all(msg.as_bytes()).await?;
        sender.shutdown().await?;

        Ok(())
    }

    pub async fn listen(
        &self,
        pipe_sender: Sender<AuthorizationResponse>,
    ) -> Result<(), PipeError> {
        // todo: give state so we can gracefully shutdown the pipe
        let mut quit_signal = self.quit_signal.resubscribe();
        let listener = PipeListenerOptions::new()
            .path(Path::new(&format!(r"\\.\pipe\{}", PIPE_NAME)))
            .create_tokio_duplex::<pipe_mode::Bytes>()?;

        loop {
            select! {
                Ok(conn) = listener.accept() => {
                    let new_sender = pipe_sender.clone();
                    tokio::spawn(async move {
                        if let Err(e) = Self::handle_conn(conn, new_sender).await {
                            tracing::error!("error while handling connection: {e}");
                        }
                    });
                },
                _ = sleep(Duration::from_millis(100)) => {
                    if let Ok(Event::Quit) = quit_signal.try_recv() {
                        tracing::info!("Shutting down Code Listener");
                        break;
                    }
                }
            }
        }

        tracing::info!("Code Listener Shutdown");
        Ok(())
    }

    async fn handle_conn(
        conn: DuplexPipeStream<pipe_mode::Bytes>,
        pipe_sender: Sender<AuthorizationResponse>,
    ) -> Result<(), PipeError> {
        let (mut recver, _) = conn.split();

        let mut buffer = Vec::with_capacity(512);

        recver.read_buf(&mut buffer).await?;

        let as_string = String::from_utf8_lossy(&buffer).to_string();
        let response: AuthorizationResponse = as_string.try_into()?;

        let _ = pipe_sender.send(response);
        Ok(())
    }
}
