#[cfg(not(coverage))]
use anyhow::{Context, Result};

#[cfg(not(coverage))]
use {
    futures::stream,
    lesavka_common::lesavka::{
        self as pb, AudioPacket, Empty, HandshakeSet, KeyboardReport, MonitorRequest, MouseReport,
        OutputDelayProbeReply, OutputDelayProbeRequest, PasteReply, PasteRequest,
        UpstreamMediaBundle, VideoPacket,
        handshake_server::{Handshake, HandshakeServer},
        relay_server::{Relay, RelayServer},
    },
    serde_json::json,
    std::{
        fs::{File, OpenOptions},
        io::Write,
        net::SocketAddr,
        path::PathBuf,
        pin::Pin,
        sync::{Arc, Mutex},
        time::{SystemTime, UNIX_EPOCH},
    },
    tonic::{Request, Response, Status, transport::Server},
};

#[cfg(not(coverage))]
#[derive(Clone)]
struct DummyConfig {
    camera_codec: String,
    width: u32,
    height: u32,
    fps: u32,
    bundled: bool,
    microphone: bool,
}

#[cfg(not(coverage))]
#[derive(Clone)]
struct CaptureSink {
    packet_dir: Arc<PathBuf>,
    metrics: Arc<Mutex<File>>,
    packet_index: Arc<std::sync::atomic::AtomicU64>,
}

#[cfg(not(coverage))]
impl CaptureSink {
    fn new(artifact_dir: PathBuf) -> Result<Self> {
        std::fs::create_dir_all(&artifact_dir)
            .with_context(|| format!("creating {}", artifact_dir.display()))?;
        let packet_dir = artifact_dir.join("video-packets");
        std::fs::create_dir_all(&packet_dir)
            .with_context(|| format!("creating {}", packet_dir.display()))?;
        let metrics = OpenOptions::new()
            .create(true)
            .append(true)
            .open(artifact_dir.join("uplink-media.jsonl"))
            .with_context(|| {
                format!(
                    "opening {}",
                    artifact_dir.join("uplink-media.jsonl").display()
                )
            })?;
        Ok(Self {
            packet_dir: Arc::new(packet_dir),
            metrics: Arc::new(Mutex::new(metrics)),
            packet_index: Arc::new(std::sync::atomic::AtomicU64::new(0)),
        })
    }

    fn record_video_packet(
        &self,
        stream_name: &str,
        bundle_seq: Option<u64>,
        packet: &VideoPacket,
    ) -> Result<()> {
        let idx = self
            .packet_index
            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
        let file_name = format!("{idx:08}_{stream_name}_seq{:08}.bin", packet.seq);
        let packet_path = self.packet_dir.join(&file_name);
        std::fs::write(&packet_path, &packet.data)
            .with_context(|| format!("writing {}", packet_path.display()))?;
        let unix_ms = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_millis();
        let line = json!({
            "schema": "lesavka.client-uplink-dummy-server.packet.v1",
            "unix_ms": unix_ms,
            "stream": stream_name,
            "bundle_seq": bundle_seq,
            "packet_seq": packet.seq,
            "packet_pts": packet.pts,
            "bytes": packet.data.len(),
            "file": file_name,
            "client_capture_pts_us": packet.client_capture_pts_us,
            "client_send_pts_us": packet.client_send_pts_us,
            "client_queue_depth": packet.client_queue_depth,
            "client_queue_age_ms": packet.client_queue_age_ms,
        });
        let mut metrics = self.metrics.lock().expect("metrics lock");
        writeln!(metrics, "{line}")?;
        Ok(())
    }
}

#[cfg(not(coverage))]
#[derive(Clone)]
struct DummyRelay {
    sink: CaptureSink,
}

#[cfg(not(coverage))]
#[tonic::async_trait]
impl Relay for DummyRelay {
    type StreamKeyboardStream =
        Pin<Box<dyn futures::Stream<Item = Result<KeyboardReport, Status>> + Send>>;
    type StreamMouseStream =
        Pin<Box<dyn futures::Stream<Item = Result<MouseReport, Status>> + Send>>;
    type CaptureVideoStream =
        Pin<Box<dyn futures::Stream<Item = Result<VideoPacket, Status>> + Send>>;
    type CaptureAudioStream =
        Pin<Box<dyn futures::Stream<Item = Result<AudioPacket, Status>> + Send>>;
    type StreamMicrophoneStream =
        Pin<Box<dyn futures::Stream<Item = Result<Empty, Status>> + Send>>;
    type StreamCameraStream = Pin<Box<dyn futures::Stream<Item = Result<Empty, Status>> + Send>>;
    type StreamWebcamMediaStream =
        Pin<Box<dyn futures::Stream<Item = Result<Empty, Status>> + Send>>;
    type RunOutputDelayProbeStream =
        Pin<Box<dyn futures::Stream<Item = Result<OutputDelayProbeReply, Status>> + Send>>;

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

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

    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> {
        let mut inbound = request.into_inner();
        tokio::spawn(async move { while matches!(inbound.message().await, Ok(Some(_))) {} });
        Ok(Response::new(Box::pin(stream::pending())))
    }

    async fn stream_camera(
        &self,
        request: Request<tonic::Streaming<VideoPacket>>,
    ) -> Result<Response<Self::StreamCameraStream>, Status> {
        let mut inbound = request.into_inner();
        let sink = self.sink.clone();
        tokio::spawn(async move {
            while let Ok(Some(packet)) = inbound.message().await {
                if let Err(err) = sink.record_video_packet("stream-camera", None, &packet) {
                    eprintln!("dummy uplink server failed to record camera packet: {err:#}");
                }
            }
        });
        Ok(Response::new(Box::pin(stream::pending())))
    }

    async fn stream_webcam_media(
        &self,
        request: Request<tonic::Streaming<UpstreamMediaBundle>>,
    ) -> Result<Response<Self::StreamWebcamMediaStream>, Status> {
        let mut inbound = request.into_inner();
        let sink = self.sink.clone();
        tokio::spawn(async move {
            while let Ok(Some(bundle)) = inbound.message().await {
                if let Some(packet) = bundle.video.as_ref()
                    && let Err(err) =
                        sink.record_video_packet("stream-webcam-media", Some(bundle.seq), packet)
                {
                    eprintln!("dummy uplink server failed to record bundled packet: {err:#}");
                }
            }
        });
        Ok(Response::new(Box::pin(stream::pending())))
    }

    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> {
        Ok(Response::new(PasteReply {
            ok: true,
            error: String::new(),
        }))
    }

    async fn reset_usb(
        &self,
        _request: Request<Empty>,
    ) -> Result<Response<pb::ResetUsbReply>, Status> {
        Ok(Response::new(pb::ResetUsbReply { ok: true }))
    }

    async fn recover_usb(
        &self,
        _request: Request<Empty>,
    ) -> Result<Response<pb::ResetUsbReply>, Status> {
        self.reset_usb(Request::new(Empty {})).await
    }

    async fn recover_uac(
        &self,
        _request: Request<Empty>,
    ) -> Result<Response<pb::ResetUsbReply>, Status> {
        self.reset_usb(Request::new(Empty {})).await
    }

    async fn recover_uvc(
        &self,
        _request: Request<Empty>,
    ) -> Result<Response<pb::ResetUsbReply>, Status> {
        self.reset_usb(Request::new(Empty {})).await
    }

    async fn get_capture_power(
        &self,
        _request: Request<Empty>,
    ) -> Result<Response<pb::CapturePowerState>, Status> {
        Ok(Response::new(pb::CapturePowerState {
            available: true,
            enabled: true,
            unit: "dummy-local-server".to_string(),
            detail: "client uplink probe".to_string(),
            active_leases: 1,
            mode: "force-on".to_string(),
            detected_devices: 1,
        }))
    }

    async fn set_capture_power(
        &self,
        _request: Request<pb::SetCapturePowerRequest>,
    ) -> Result<Response<pb::CapturePowerState>, Status> {
        self.get_capture_power(Request::new(Empty {})).await
    }

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

    async fn calibrate(
        &self,
        _request: Request<pb::CalibrationRequest>,
    ) -> Result<Response<pb::CalibrationState>, Status> {
        self.get_calibration(Request::new(Empty {})).await
    }

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

#[cfg(not(coverage))]
#[derive(Clone)]
struct DummyHandshake {
    config: DummyConfig,
}

#[cfg(not(coverage))]
#[tonic::async_trait]
impl Handshake for DummyHandshake {
    async fn get_capabilities(
        &self,
        _request: Request<Empty>,
    ) -> Result<Response<HandshakeSet>, Status> {
        Ok(Response::new(HandshakeSet {
            camera: true,
            microphone: self.config.microphone,
            bundled_webcam_media: self.config.bundled,
            camera_output: "dummy-uplink-capture".to_string(),
            camera_codec: self.config.camera_codec.clone(),
            camera_width: self.config.width,
            camera_height: self.config.height,
            camera_fps: self.config.fps,
            eye_width: self.config.width,
            eye_height: self.config.height,
            eye_fps: self.config.fps,
            server_version: "client-uplink-dummy".to_string(),
            server_revision: String::new(),
        }))
    }
}

#[cfg(not(coverage))]
fn arg_value(args: &[String], name: &str, default: &str) -> String {
    args.windows(2)
        .find_map(|pair| (pair[0] == name).then(|| pair[1].clone()))
        .unwrap_or_else(|| default.to_string())
}

#[cfg(not(coverage))]
fn arg_flag(args: &[String], name: &str, default: bool) -> bool {
    args.windows(2)
        .find_map(|pair| {
            (pair[0] == name).then(|| {
                let raw = pair[1].trim().to_ascii_lowercase();
                !matches!(raw.as_str(), "0" | "false" | "no" | "off")
            })
        })
        .unwrap_or(default)
}

#[cfg(not(coverage))]
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
    let args = std::env::args().skip(1).collect::<Vec<_>>();
    if args.iter().any(|arg| arg == "--help" || arg == "-h") {
        println!(
            "Usage: lesavka-uplink-dummy-server --listen 127.0.0.1:50051 --artifact-dir DIR --width 1280 --height 720 --fps 30 --codec mjpeg --bundled true --microphone false"
        );
        return Ok(());
    }
    let listen: SocketAddr = arg_value(&args, "--listen", "127.0.0.1:50051")
        .parse()
        .context("parsing --listen")?;
    let artifact_dir = PathBuf::from(arg_value(
        &args,
        "--artifact-dir",
        "artifacts/client-uplink-dummy",
    ));
    let config = DummyConfig {
        camera_codec: arg_value(&args, "--codec", "mjpeg"),
        width: arg_value(&args, "--width", "1280")
            .parse()
            .context("parsing --width")?,
        height: arg_value(&args, "--height", "720")
            .parse()
            .context("parsing --height")?,
        fps: arg_value(&args, "--fps", "30")
            .parse()
            .context("parsing --fps")?,
        bundled: arg_flag(&args, "--bundled", false),
        microphone: arg_flag(&args, "--microphone", false),
    };
    std::fs::create_dir_all(&artifact_dir)
        .with_context(|| format!("creating {}", artifact_dir.display()))?;
    std::fs::write(
        artifact_dir.join("dummy-server-command.txt"),
        std::env::args().collect::<Vec<_>>().join(" "),
    )
    .ok();
    let sink = CaptureSink::new(artifact_dir)?;
    eprintln!("lesavka uplink dummy server listening on {listen}");
    Server::builder()
        .add_service(RelayServer::new(DummyRelay { sink }))
        .add_service(HandshakeServer::new(DummyHandshake { config }))
        .serve(listen)
        .await
        .context("serving dummy uplink server")?;
    Ok(())
}

#[cfg(coverage)]
fn main() {}
