use anyhow::{Context, Result};
use lesavka_common::lesavka::{
    CapturePowerCommand, Empty, SetCapturePowerRequest, relay_client::RelayClient,
};
use tonic::{Request, transport::Channel};

use super::state::CapturePowerStatus;
use crate::relay_transport;

pub fn fetch_capture_power(server_addr: &str) -> Result<CapturePowerStatus> {
    with_runtime(async move {
        let mut client = connect(server_addr).await?;
        let reply = client
            .get_capture_power(Request::new(Empty {}))
            .await
            .context("querying capture power state")?
            .into_inner();
        Ok(map_state(reply))
    })
}

pub fn set_capture_power_mode(
    server_addr: &str,
    command: CapturePowerCommand,
) -> Result<CapturePowerStatus> {
    with_runtime(async move {
        let mut client = connect(server_addr).await?;
        let reply = client
            .set_capture_power(Request::new(SetCapturePowerRequest {
                enabled: matches!(command, CapturePowerCommand::ForceOn),
                command: command as i32,
            }))
            .await
            .context("setting capture power state")?
            .into_inner();
        Ok(map_state(reply))
    })
}

pub fn recover_usb_soft(server_addr: &str) -> Result<()> {
    run_recovery(server_addr, RecoveryKind::Usb)
}

pub fn recover_uac_soft(server_addr: &str) -> Result<()> {
    run_recovery(server_addr, RecoveryKind::Uac)
}

pub fn recover_uvc_soft(server_addr: &str) -> Result<()> {
    run_recovery(server_addr, RecoveryKind::Uvc)
}

#[derive(Clone, Copy)]
enum RecoveryKind {
    Usb,
    Uac,
    Uvc,
}

fn run_recovery(server_addr: &str, kind: RecoveryKind) -> Result<()> {
    with_runtime(async move {
        let mut client = connect(server_addr).await?;
        let request = Request::new(Empty {});
        let response = match kind {
            RecoveryKind::Usb => client
                .recover_usb(request)
                .await
                .context("requesting soft USB recovery")?,
            RecoveryKind::Uac => client
                .recover_uac(request)
                .await
                .context("requesting soft UAC recovery")?,
            RecoveryKind::Uvc => client
                .recover_uvc(request)
                .await
                .context("requesting soft UVC recovery")?,
        };
        let reply = response.into_inner();
        if reply.ok {
            Ok(())
        } else {
            match kind {
                RecoveryKind::Usb => anyhow::bail!("relay reported soft USB recovery failure"),
                RecoveryKind::Uac => anyhow::bail!("relay reported soft UAC recovery failure"),
                RecoveryKind::Uvc => anyhow::bail!("relay reported soft UVC recovery failure"),
            }
        }
    })
}

fn with_runtime<F, T>(future: F) -> Result<T>
where
    F: std::future::Future<Output = Result<T>>,
{
    tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .context("building launcher power runtime")?
        .block_on(future)
}

async fn connect(server_addr: &str) -> Result<RelayClient<Channel>> {
    let channel = relay_transport::endpoint(server_addr)?
        .tcp_nodelay(true)
        .connect()
        .await
        .context("connecting launcher to relay host")?;
    Ok(RelayClient::new(channel))
}

fn map_state(reply: lesavka_common::lesavka::CapturePowerState) -> CapturePowerStatus {
    CapturePowerStatus {
        available: reply.available,
        enabled: reply.enabled,
        unit: reply.unit,
        detail: reply.detail,
        active_leases: reply.active_leases,
        mode: reply.mode,
        detected_devices: reply.detected_devices,
    }
}
