mod prox;

use std::{collections::HashMap, sync::LazyLock};

use prox::{packet::HYP_UUID_SIZE, ProximityPlayer};
use tokio::sync::RwLock;
use tracing::{error, info_span, Instrument};
use wtransport::{Endpoint, Identity, ServerConfig};
use xtra::{Actor, Address, Handler, Mailbox};
use zerocopy::{FromZeros, Immutable, IntoBytes};

use crate::{
  packet::{CLIENT_NAME_SIZE, STAGE_GAME_NAME_SIZE},
  protocol::String,
};

type UuidString = String<HYP_UUID_SIZE>;

fn listeners() -> &'static RwLock<HashMap<UuidString, Address<ProximityPlayer>>> {
  static LISTENERS: LazyLock<RwLock<HashMap<UuidString, Address<ProximityPlayer>>>> = LazyLock::new(Default::default);

  &LISTENERS
}

pub fn web_main() -> Address<Manager> {
  let span = info_span!("wt");

  let manager = xtra::spawn_tokio(
    Manager {
      players: HashMap::new(),
      next_id: 0,
    },
    Mailbox::bounded(8),
  );
  tokio::spawn({
    let manager = manager.clone();
    async move {
      if let Err(result) = webtransport_server(manager).await {
        error!("{:?}", result);
      }
    }
    .instrument(span)
  });
  manager
}

async fn webtransport_server(manager: Address<Manager>) -> anyhow::Result<()> {
  // let identity = Identity::self_signed(["localhost", "127.0.0.1", "::1"]).unwrap();
  let identity = Identity::load_pemfiles("./cert.pem", "./key.pem").await.unwrap();

  let config = ServerConfig::builder().with_bind_default(4433).with_identity(identity).build();

  let endpoint = Endpoint::server(config)?;

  loop {
    let connection = endpoint.accept().await;

    ProximityPlayer::spawn(connection, manager.clone());
  }
}

#[derive(Actor)]
pub struct Manager {
  players: HashMap<u128, PlayerInstance>,
  next_id: u32,
}

impl Manager {
  pub async fn broadcast<M: Copy + Send + 'static>(&self, message: M)
  where
    ProximityPlayer: Handler<M>,
  {
    for player in listeners().read().await.values() {
      let _ = player.send(message).detach().await;
    }
  }
}

#[derive(Debug, Clone, Copy, IntoBytes, Immutable)]
#[repr(C, packed)]
struct PlayerInstance {
  id: u32,
  name: String<CLIENT_NAME_SIZE>,
  stage: String<STAGE_GAME_NAME_SIZE>,
  position: [f32; 3],
}

struct RequestState;
impl Handler<RequestState> for Manager {
  type Return = HashMap<u128, PlayerInstance>;

  async fn handle(&mut self, _: RequestState, _: &mut xtra::Context<Self>) -> Self::Return {
    self.players.clone()
  }
}

#[derive(Debug, Clone, Copy, IntoBytes, Immutable)]
#[repr(C)]
pub struct PlayerConnected {
  pub id: u128,
  pub name: String<CLIENT_NAME_SIZE>,
}

impl Handler<PlayerConnected> for Manager {
  type Return = ();

  async fn handle(&mut self, message: PlayerConnected, _: &mut xtra::Context<Self>) -> Self::Return {
    let id = {
          let id = self.next_id;
          self.next_id += 1;
          id
        };
    self.players.insert(
      message.id,
      PlayerInstance {
        id,
        name: message.name,
        position: [0.0; 3],
        stage: String::new_zeroed(),
      },
    );
    self.broadcast(PlayerConnected { id: id as u128, name: message.name}).await;
  }
}

#[derive(Debug, Clone, Copy, IntoBytes, Immutable)]
#[repr(C)]
pub struct PlayerDisconnected {
  pub id: u128,
}

impl Handler<PlayerDisconnected> for Manager {
  type Return = ();

  async fn handle(&mut self, message: PlayerDisconnected, _: &mut xtra::Context<Self>) -> Self::Return {
    if let Some(player) = self.players.remove(&message.id) {
      self.broadcast(PlayerDisconnected { id: player.id as u128 }).await;
    }
  }
}

#[derive(Debug, Clone, Copy, IntoBytes, Immutable)]
#[repr(C, packed)]
pub struct PlayerMoved {
  pub id: u128,
  pub position: [f32; 3],
}

impl Handler<PlayerMoved> for Manager {
  type Return = ();

  async fn handle(&mut self, message: PlayerMoved, _: &mut xtra::Context<Self>) -> Self::Return {
    let id = message.id;
    if let Some(player) = self.players.get_mut(&id) {
      player.position = message.position;
      let id = player.id;
      self
        .broadcast(PlayerMoved {
          id: id as u128,
          ..message
        })
        .await;
    }
  }
}

#[derive(Debug, Clone, Copy, IntoBytes, Immutable)]
#[repr(C, packed)]
pub struct ChangedStage {
  pub id: u128,
  pub stage: String<STAGE_GAME_NAME_SIZE>,
}

impl Handler<ChangedStage> for Manager {
  type Return = ();

  async fn handle(&mut self, message: ChangedStage, _: &mut xtra::Context<Self>) -> Self::Return {
    let id = message.id;
    if let Some(player) = self.players.get_mut(&id) {
      player.stage = message.stage;
      let id = player.id;
      self
        .broadcast(ChangedStage {
          id: id as u128,
          ..message
        })
        .await;
    }
  }
}
