use std::sync::mpsc;
use std::thread;
use std::time::Duration;

use super::{ SystemState, WifiState };
use super::{ audio, brightness, network };

pub struct SystemPoller
{
	receiver: mpsc::Receiver<SystemState>,
}

impl SystemPoller
{
	pub fn new() -> Self
	{
		let ( sender, receiver ) = mpsc::channel();

		if let Err( e ) = thread::Builder::new()
			.name( "system-poller".into() )
			.spawn( move || poll_loop( sender ) )
		{
			tracing::error!( "Failed to spawn system poller thread: {}", e );
		}

		Self { receiver }
	}

	/// Get the latest system state, discarding any older buffered states.
	pub fn try_recv( &self ) -> Option<SystemState>
	{
		let mut latest = None;
		while let Ok( state ) = self.receiver.try_recv()
		{
			latest = Some( state );
		}
		latest
	}
}

fn poll_loop( sender: mpsc::Sender<SystemState> )
{
	let system_conn = zbus::blocking::Connection::system().ok();

	// Send initial state immediately
	if !send_state( &sender, system_conn.as_ref() )
	{
		return;
	}

	loop
	{
		thread::sleep( Duration::from_secs( 15 ) );
		if !send_state( &sender, system_conn.as_ref() )
		{
			break;
		}
	}
}

fn send_state(
	sender: &mpsc::Sender<SystemState>,
	conn: Option<&zbus::blocking::Connection>,
) -> bool
{
	let state = SystemState {
		wifi: get_wifi_dbus( conn ),
		bluetooth: network::get_bluetooth_state(),
		audio: audio::get_audio_state(),
		brightness: brightness::get_brightness(),
	};
	sender.send( state ).is_ok()
}

// ==========================================================================
// D-Bus property helpers
// ==========================================================================

const NM_DEST: &str = "org.freedesktop.NetworkManager";
const NM_PATH: &str = "/org/freedesktop/NetworkManager";
const NM_IFACE: &str = "org.freedesktop.NetworkManager";
const NM_CONN_ACTIVE_IFACE: &str = "org.freedesktop.NetworkManager.Connection.Active";

fn get_prop(
	conn: &zbus::blocking::Connection,
	dest: &str,
	path: &str,
	iface: &str,
	prop: &str,
) -> Option<zbus::zvariant::OwnedValue>
{
	let reply = conn
		.call_method(
			Some( dest ),
			path,
			Some( "org.freedesktop.DBus.Properties" ),
			"Get",
			&( iface, prop ),
		)
		.ok()?;
	reply.body().deserialize().ok()
}

fn get_prop_bool(
	conn: &zbus::blocking::Connection,
	dest: &str,
	path: &str,
	iface: &str,
	prop: &str,
) -> Option<bool>
{
	let val = get_prop( conn, dest, path, iface, prop )?;
	bool::try_from( val ).ok()
}

fn get_prop_string(
	conn: &zbus::blocking::Connection,
	dest: &str,
	path: &str,
	iface: &str,
	prop: &str,
) -> Option<String>
{
	let val = get_prop( conn, dest, path, iface, prop )?;
	String::try_from( val ).ok()
}

fn get_prop_object_path(
	conn: &zbus::blocking::Connection,
	dest: &str,
	path: &str,
	iface: &str,
	prop: &str,
) -> Option<String>
{
	let val = get_prop( conn, dest, path, iface, prop )?;
	let op: zbus::zvariant::OwnedObjectPath = val.try_into().ok()?;
	Some( op.into_inner().to_string() )
}

// ==========================================================================
// WiFi via NetworkManager D-Bus
// ==========================================================================

fn get_wifi_dbus( conn: Option<&zbus::blocking::Connection> ) -> WifiState
{
	let Some( conn ) = conn else
	{
		return WifiState::default();
	};

	let enabled = get_prop_bool( conn, NM_DEST, NM_PATH, NM_IFACE, "WirelessEnabled" )
		.unwrap_or( false );

	if !enabled
	{
		return WifiState {
			enabled: false,
			..Default::default()
		};
	}

	// Check primary connection type
	let conn_type =
		get_prop_string( conn, NM_DEST, NM_PATH, NM_IFACE, "PrimaryConnectionType" )
			.unwrap_or_default();

	if conn_type == "802-11-wireless"
	{
		let primary = get_prop_object_path( conn, NM_DEST, NM_PATH, NM_IFACE, "PrimaryConnection" );

		let ssid = primary.as_ref().and_then( |path|
		{
			get_prop_string( conn, NM_DEST, path, NM_CONN_ACTIVE_IFACE, "Id" )
		} );

		return WifiState {
			enabled: true,
			connected: true,
			ssid,
		};
	}

	WifiState {
		enabled: true,
		connected: false,
		ssid: None,
	}
}
