use super::super::{
    clipboard::send_clipboard_text_to_remote,
    power::{recover_uac_soft, recover_usb_soft, recover_uvc_soft},
};
use futures::stream;
use lesavka_common::lesavka::{
    AudioPacket, CalibrationRequest, CalibrationState, CapturePowerState, Empty, KeyboardReport,
    MonitorRequest, MouseReport, OutputDelayProbeReply, OutputDelayProbeRequest, PasteReply,
    PasteRequest, ResetUsbReply, SetCapturePowerRequest, UpstreamMediaBundle, UpstreamSyncState,
    VideoPacket,
    relay_server::{Relay, RelayServer},
};
use serial_test::serial;
use std::{
    pin::Pin,
    sync::{Arc, Mutex},
    time::Duration,
};
use tonic::{Request, Response, Status};

type KeyboardStream = Pin<Box<dyn futures::Stream<Item = Result<KeyboardReport, Status>> + Send>>;
type MouseStream = Pin<Box<dyn futures::Stream<Item = Result<MouseReport, Status>> + Send>>;
type VideoStream = Pin<Box<dyn futures::Stream<Item = Result<VideoPacket, Status>> + Send>>;
type AudioStream = Pin<Box<dyn futures::Stream<Item = Result<AudioPacket, Status>> + Send>>;
type EmptyStream = Pin<Box<dyn futures::Stream<Item = Result<Empty, Status>> + Send>>;
type OutputDelayProbeStream =
    Pin<Box<dyn futures::Stream<Item = Result<OutputDelayProbeReply, Status>> + Send>>;

#[derive(Clone)]
struct UtilityRelay {
    paste_count: Arc<Mutex<usize>>,
    reset_count: Arc<Mutex<usize>>,
    reset_ok: bool,
}

impl UtilityRelay {
    fn new(reset_ok: bool) -> Self {
        Self {
            paste_count: Arc::new(Mutex::new(0)),
            reset_count: Arc::new(Mutex::new(0)),
            reset_ok,
        }
    }
}

#[tonic::async_trait]
impl Relay for UtilityRelay {
    type StreamKeyboardStream = KeyboardStream;
    type StreamMouseStream = MouseStream;
    type CaptureVideoStream = VideoStream;
    type CaptureAudioStream = AudioStream;
    type StreamMicrophoneStream = EmptyStream;
    type StreamCameraStream = EmptyStream;
    type StreamWebcamMediaStream = EmptyStream;
    type RunOutputDelayProbeStream = OutputDelayProbeStream;

    async fn stream_keyboard(
        &self,
        _request: Request<tonic::Streaming<KeyboardReport>>,
    ) -> Result<Response<Self::StreamKeyboardStream>, Status> {
        Ok(Response::new(Box::pin(stream::empty())))
    }

    async fn stream_mouse(
        &self,
        _request: Request<tonic::Streaming<MouseReport>>,
    ) -> Result<Response<Self::StreamMouseStream>, Status> {
        Ok(Response::new(Box::pin(stream::empty())))
    }

    async fn capture_video(
        &self,
        _request: Request<MonitorRequest>,
    ) -> Result<Response<Self::CaptureVideoStream>, Status> {
        Ok(Response::new(Box::pin(stream::empty())))
    }

    async fn capture_audio(
        &self,
        _request: Request<MonitorRequest>,
    ) -> Result<Response<Self::CaptureAudioStream>, Status> {
        Ok(Response::new(Box::pin(stream::empty())))
    }

    async fn stream_microphone(
        &self,
        _request: Request<tonic::Streaming<AudioPacket>>,
    ) -> Result<Response<Self::StreamMicrophoneStream>, Status> {
        Ok(Response::new(Box::pin(stream::empty())))
    }

    async fn stream_camera(
        &self,
        _request: Request<tonic::Streaming<VideoPacket>>,
    ) -> Result<Response<Self::StreamCameraStream>, Status> {
        Ok(Response::new(Box::pin(stream::empty())))
    }

    async fn stream_webcam_media(
        &self,
        _request: Request<tonic::Streaming<UpstreamMediaBundle>>,
    ) -> Result<Response<Self::StreamWebcamMediaStream>, Status> {
        Ok(Response::new(Box::pin(stream::empty())))
    }

    async fn run_output_delay_probe(
        &self,
        _request: Request<OutputDelayProbeRequest>,
    ) -> Result<Response<Self::RunOutputDelayProbeStream>, Status> {
        Ok(Response::new(Box::pin(stream::empty())))
    }

    async fn paste_text(
        &self,
        _request: Request<PasteRequest>,
    ) -> Result<Response<PasteReply>, Status> {
        *self.paste_count.lock().expect("paste count") += 1;
        Ok(Response::new(PasteReply {
            ok: true,
            error: String::new(),
        }))
    }

    async fn reset_usb(&self, _request: Request<Empty>) -> Result<Response<ResetUsbReply>, Status> {
        *self.reset_count.lock().expect("reset count") += 1;
        Ok(Response::new(ResetUsbReply { ok: self.reset_ok }))
    }

    async fn recover_usb(
        &self,
        _request: Request<Empty>,
    ) -> Result<Response<ResetUsbReply>, Status> {
        *self.reset_count.lock().expect("reset count") += 1;
        Ok(Response::new(ResetUsbReply { ok: self.reset_ok }))
    }

    async fn recover_uac(
        &self,
        _request: Request<Empty>,
    ) -> Result<Response<ResetUsbReply>, Status> {
        *self.reset_count.lock().expect("reset count") += 1;
        Ok(Response::new(ResetUsbReply { ok: self.reset_ok }))
    }

    async fn recover_uvc(
        &self,
        _request: Request<Empty>,
    ) -> Result<Response<ResetUsbReply>, Status> {
        *self.reset_count.lock().expect("reset count") += 1;
        Ok(Response::new(ResetUsbReply { ok: self.reset_ok }))
    }

    async fn get_capture_power(
        &self,
        _request: Request<Empty>,
    ) -> Result<Response<CapturePowerState>, Status> {
        Ok(Response::new(CapturePowerState::default()))
    }

    async fn set_capture_power(
        &self,
        _request: Request<SetCapturePowerRequest>,
    ) -> Result<Response<CapturePowerState>, Status> {
        Ok(Response::new(CapturePowerState::default()))
    }

    async fn get_calibration(
        &self,
        _request: Request<Empty>,
    ) -> Result<Response<CalibrationState>, Status> {
        Ok(Response::new(CalibrationState::default()))
    }

    async fn calibrate(
        &self,
        _request: Request<CalibrationRequest>,
    ) -> Result<Response<CalibrationState>, Status> {
        Ok(Response::new(CalibrationState::default()))
    }

    async fn get_upstream_sync(
        &self,
        _request: Request<Empty>,
    ) -> Result<Response<UpstreamSyncState>, Status> {
        Ok(Response::new(UpstreamSyncState::default()))
    }
}

fn serve(relay: UtilityRelay) -> (tokio::runtime::Runtime, String) {
    let rt = tokio::runtime::Runtime::new().expect("runtime");
    let addr = rt.block_on(async {
        let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind relay");
        let addr = listener.local_addr().expect("relay addr");
        drop(listener);
        tokio::spawn(async move {
            let _ = tonic::transport::Server::builder()
                .add_service(RelayServer::new(relay))
                .serve(addr)
                .await;
        });
        addr
    });
    std::thread::sleep(Duration::from_millis(50));
    (rt, format!("http://{addr}"))
}

#[test]
#[serial]
fn clipboard_action_delivers_text_over_rpc_when_key_is_available() {
    temp_env::with_vars(
        [
            (
                "LESAVKA_PASTE_KEY",
                Some("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"),
            ),
            ("LESAVKA_PASTE_KEY_FILE", None::<&str>),
            ("LESAVKA_CLIPBOARD_TIMEOUT_MS", Some("1500")),
        ],
        || {
            let relay = UtilityRelay::new(true);
            let paste_count = Arc::clone(&relay.paste_count);
            let (_rt, addr) = serve(relay);

            let result =
                send_clipboard_text_to_remote(&addr, "hello from launcher").expect("clipboard RPC");

            assert_eq!(result, "Clipboard delivered to remote");
            assert_eq!(*paste_count.lock().expect("paste count"), 1);
        },
    );
}

#[test]
#[serial]
fn recover_usb_action_reports_relay_success_and_failure() {
    let relay = UtilityRelay::new(true);
    let reset_count = Arc::clone(&relay.reset_count);
    let (_rt, addr) = serve(relay);
    recover_usb_soft(&addr).expect("successful soft USB recovery");
    assert_eq!(*reset_count.lock().expect("reset count"), 1);

    let relay = UtilityRelay::new(false);
    let reset_count = Arc::clone(&relay.reset_count);
    let (_rt, addr) = serve(relay);
    let err = recover_usb_soft(&addr).expect_err("soft USB recovery failure");
    assert!(format!("{err:#}").contains("relay reported soft USB recovery failure"));
    assert_eq!(*reset_count.lock().expect("reset count"), 1);
}

#[test]
#[serial]
fn soft_recovery_actions_report_relay_success() {
    let relay = UtilityRelay::new(true);
    let reset_count = Arc::clone(&relay.reset_count);
    let (_rt, addr) = serve(relay);
    recover_usb_soft(&addr).expect("usb soft recover");
    recover_uac_soft(&addr).expect("uac soft recover");
    recover_uvc_soft(&addr).expect("uvc soft recover");
    assert_eq!(*reset_count.lock().expect("reset count"), 3);
}
