use std::{future::Future, pin::Pin, sync::Arc};

use async_trait::async_trait;
use rivetkit::prelude::*;
use rivetkit::{Action, Event, Handles, action};
use serde::{Deserialize, Serialize};

pub const ACTOR_NAME: &str = "counter";

type BoxFuture<T> = Pin<Box<dyn Future<Output = Result<T>> + Send>>;

pub struct Counter;

#[derive(Default, Serialize, Deserialize)]
pub struct CounterState {
	pub count: i64,
}

#[derive(Default, Serialize, Deserialize)]
pub struct CounterConnParams {
	pub label: String,
}

#[derive(Clone, Default, Serialize, Deserialize)]
pub struct CounterConnState {
	pub label: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Increment {
	pub amount: i64,
}

impl Action for Increment {
	type Output = i64;

	const NAME: &'static str = "increment";
}

#[derive(Debug, Serialize, Deserialize)]
pub struct GetCount;

impl Action for GetCount {
	type Output = i64;

	const NAME: &'static str = "getCount";
}

#[derive(Debug, Serialize, Deserialize)]
pub struct GetConnLabel;

impl Action for GetConnLabel {
	type Output = String;

	const NAME: &'static str = "getConnLabel";
}

#[derive(Debug, Serialize, Deserialize)]
pub struct NewCount {
	pub count: i64,
}

impl Event for NewCount {
	const NAME: &'static str = "newCount";
}

#[async_trait]
impl Actor for Counter {
	type State = CounterState;
	type Input = ();
	type Actions = (Increment, GetCount, GetConnLabel);
	type Events = (NewCount,);
	type Queue = ();
	type ConnParams = CounterConnParams;
	type ConnState = CounterConnState;
	type Action = action::Raw;

	async fn create_state(_ctx: &Ctx<Self>, _input: Self::Input) -> Result<Self::State> {
		Ok(CounterState::default())
	}

	async fn create(_ctx: &Ctx<Self>) -> Result<Self> {
		Ok(Self)
	}

	async fn create_conn_state(
		self: Arc<Self>,
		_ctx: Ctx<Self>,
		params: Self::ConnParams,
	) -> Result<Self::ConnState> {
		Ok(CounterConnState {
			label: params.label,
		})
	}
}

impl Handles<Increment> for Counter {
	type Future = BoxFuture<i64>;

	fn handle(self: Arc<Self>, ctx: Ctx<Self>, action: Increment) -> Self::Future {
		Box::pin(async move {
			let count = {
				let mut state = ctx.state_mut();
				state.count += action.amount;
				state.count
			};
			ctx.emit(NewCount { count })?;
			Ok(count)
		})
	}
}

impl Handles<GetCount> for Counter {
	type Future = BoxFuture<i64>;

	fn handle(self: Arc<Self>, ctx: Ctx<Self>, _action: GetCount) -> Self::Future {
		Box::pin(async move { Ok(ctx.state().count) })
	}
}

impl Handles<GetConnLabel> for Counter {
	type Future = BoxFuture<String>;

	fn handle(self: Arc<Self>, ctx: Ctx<Self>, _action: GetConnLabel) -> Self::Future {
		Box::pin(async move {
			Ok(ctx
				.conn()
				.map(|conn| conn.state())
				.transpose()?
				.map(|state| state.label)
				.unwrap_or_default())
		})
	}
}

pub fn registry() -> Registry {
	let mut registry = Registry::new();
	registry.register_actor::<Counter>(ACTOR_NAME);
	registry
}