use std::fmt;

use busylib::model::ble::{BleState, BleStatusResponse};
use serde::Serialize;

use crate::reporter::events::CliEvent;
use crate::reporter::events::fields::{field, write_fields};

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct BleStatusEvent(BleStatusResponse);

impl BleStatusEvent {
    pub fn new(status: BleStatusResponse) -> Self {
        Self(status)
    }
}

impl From<BleStatusEvent> for CliEvent {
    fn from(event: BleStatusEvent) -> Self {
        CliEvent::BleStatus(event)
    }
}

impl fmt::Display for BleStatusEvent {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let status = &self.0;
        let mut fields = vec![field("status", ble_state_label(&status.status))];

        if let Some(address) = &status.address {
            fields.push(field("address", address));
        }

        write_fields(f, &fields)
    }
}

fn ble_state_label(state: &BleState) -> &str {
    match state {
        BleState::Reset => "reset",
        BleState::Initialization => "initialization",
        BleState::Disabled => "disabled",
        BleState::Enabled => "enabled",
        BleState::Connectable => "connectable",
        BleState::Connected => "connected",
        BleState::InternalError => "internal error",
        BleState::Unknown(state) => state,
    }
}

#[cfg(test)]
mod tests {
    use serde_json::json;

    use super::*;

    #[test]
    fn renders_the_address_only_when_connected() {
        let connected = BleStatusEvent::new(BleStatusResponse {
            status: BleState::Connected,
            address: Some("E8:D5:2B:0D:9B:20".to_string()),
        });
        let disabled = BleStatusEvent::new(BleStatusResponse {
            status: BleState::Disabled,
            address: None,
        });

        assert_eq!(
            connected.to_string(),
            "status: connected\naddress: E8:D5:2B:0D:9B:20"
        );
        assert_eq!(disabled.to_string(), "status: disabled");
    }

    #[test]
    fn keeps_the_space_in_the_internal_error_state() {
        let event = BleStatusEvent::new(BleStatusResponse {
            status: BleState::InternalError,
            address: None,
        });

        assert_eq!(event.to_string(), "status: internal error");
        assert_eq!(
            serde_json::to_value(CliEvent::from(event)).unwrap(),
            json!({"event": "ble_status", "status": "internal error", "address": null})
        );
    }
}