use std::{io::Cursor, net::SocketAddr, sync::Arc};

use tokio::{
  io::{BufReader, BufWriter},
  net::{TcpStream, UdpSocket},
  sync::mpsc,
};
use tracing::{error, info, info_span, trace, Instrument};
use xtra::{prelude::MessageChannel, scoped, Actor, Handler, Mailbox};

use crate::{
  clients, manager,
  packet::{
    rw::{read_packet, write_packet},
    ConnectionKind, Packet, PacketData,
    PacketData_variants::{Connect, HolePunch, Init, UdpInit},
    CLIENT_NAME_SIZE,
  },
  protocol::String,
  server::{ChangedStage, PlayerConnected, PlayerDisconnected, PlayerMoved},
};

pub async fn broadcast_packet(packet: Packet) {
  for (id, (address, _)) in clients().read().await.iter() {
    if *id != packet.user_id {
      let _ = address.send(packet.clone()).detach().await.unwrap();
    }
  }

  match packet.data {
    PacketData::Connect(connect) => {
      manager()
        .send(PlayerConnected {
          id: packet.user_id,
          name: connect.client_name,
        })
        .await
        .unwrap();
    }
    PacketData::Disconnect(..) => {
      manager().send(PlayerDisconnected { id: packet.user_id }).await.unwrap();
    }
    PacketData::Player(player) => {
      let _ = manager()
        .send(PlayerMoved {
          id: packet.user_id,
          position: player.position,
        })
        .detach()
        .await
        .unwrap();
    }
    PacketData::Game(game) => {
      let _ = manager()
        .send(ChangedStage {
          id: packet.user_id,
          stage: game.stage,
        })
        .detach()
        .await
        .unwrap();
    }
    _ => {}
  }
}

pub struct PlayerActor {
  id: u128,
  connection_kind: ConnectionKind,
  name: String<CLIENT_NAME_SIZE>,
  write_sender: mpsc::UnboundedSender<WriteMessage>,
}

enum WriteMessage {
  Data(Packet),
  SetUdp(u16),
}

impl PlayerActor {
  pub fn new_connection(stream: TcpStream, addr: SocketAddr, socket: Arc<UdpSocket>) {
    tokio::spawn(async move {
      info!("accepted connection from {addr}");
      let (reader, writer) = stream.into_split();
      let mut reader = BufReader::new(reader);
      let mut writer = BufWriter::new(writer);
      let packet = read_packet(&mut reader, false).await.unwrap();
      let Packet {
        user_id,
        data: PacketData::Connect(connect),
        ..
      } = packet
      else {
        tracing::error!("not a valid player!");
        return;
      };

      write_packet(&mut writer, user_id, PacketData::Init(Init { max_players: 8 }))
        .await
        .expect("msfrarausfhsdagsdgkog");

      let span = info_span!("Client", player = connect.client_name.to_string());
      let (address, mailbox) = Mailbox::bounded(512);
      let (sender, mut receiver) = mpsc::unbounded_channel();
      tokio::spawn(scoped(&address.downgrade(), {
        let address = address.downgrade();
        async move {
          let ip = addr.ip();
          let mut addr = None;
          while let Some(message) = receiver.recv().await {
            match message {
              WriteMessage::Data(Packet { user_id, udp, data }) => {
                // trace!("writing packet {udp:?}, {data:?}");
                let res = if udp {
                  async {
                    let Some(addr) = addr else {
                      trace!("no address set yet");
                      return Ok(());
                    };
                    // trace!("sending udp packet: {data:?}");

                    let mut buf = [0; 256];
                    let mut writer = Cursor::new(buf.as_mut_slice());
                    write_packet(&mut writer, user_id, data).await?;

                    socket.send_to(&buf, &addr).await.expect("kys");

                    Ok(())
                  }
                  .await
                } else {
                  write_packet(&mut writer, user_id, data).await
                };

                if let Err(error) = res {
                  error!("error while writing packet: {error:?}");
                  let _ = address.send(StopError).detach().await;
                  return;
                }
              }
              WriteMessage::SetUdp(port) => {
                trace!("set udp port, connected on udp!");
                addr = Some(SocketAddr::new(ip, port))
              }
            }
          }
        }
        .instrument(span.clone())
      }));
      tokio::spawn(scoped(
        &address.downgrade(),
        {
          let address = address.downgrade();
          async move {
            info!("connected {user_id}");

            loop {
              match read_packet(&mut reader, false).await {
                Ok(packet) => {
                  let _ = address.send(packet).detach().await;
                }
                Err(error) => {
                  error!("error while reading packet: {error:?}");
                  let _ = address.send(StopError).detach().await;
                  break;
                }
              }
            }
          }
        }
        .instrument(span.clone()),
      ));
      clients().write().await.insert(user_id, (MessageChannel::new(address), addr));
      xtra::run(
        mailbox,
        PlayerActor {
          id: user_id,
          connection_kind: connect.kind,
          name: connect.client_name,
          write_sender: sender,
        },
      )
      .instrument(span)
      .await;
    });
  }
}

impl Actor for PlayerActor {
  type Stop = ();

  async fn started(&mut self, _: &Mailbox<Self>) -> Result<(), Self::Stop> {
    broadcast_packet(Packet {
      user_id: self.id,
      udp: false,
      data: PacketData::Connect(Connect {
        kind: self.connection_kind,
        max_player: 8,
        client_name: self.name,
      }),
    })
    .await;
    self
      .write_sender
      .send(WriteMessage::Data(Packet {
        user_id: 0,
        udp: false,
        data: PacketData::UdpInit(UdpInit { port: 1027 }),
      }))
      .map_err(drop)?;

    Ok(())
  }
  async fn stopped(self) -> Self::Stop {}
}
struct StopError;
impl Handler<StopError> for PlayerActor {
  type Return = ();

  async fn handle(&mut self, _: StopError, ctx: &mut xtra::Context<Self>) -> Self::Return {
    ctx.stop_all();
  }
}

impl Handler<Packet> for PlayerActor {
  type Return = ();

  async fn handle(&mut self, packet: Packet, _: &mut xtra::Context<Self>) {
    if matches!(packet.data, PacketData::HolePunch(..)) {
      trace!("hole puncher");
      return;
    }

    if packet.user_id == self.id {
      match packet.data {
        PacketData::UdpInit(UdpInit { port }) => {
          trace!("hole puncher");
          let _ = self.write_sender.send(WriteMessage::SetUdp(port));
          let _ = self.write_sender.send(WriteMessage::Data(Packet {
            user_id: 0,
            udp: true,
            data: PacketData::HolePunch(HolePunch),
          }));
          return;
        }
        PacketData::Connect(..) | PacketData::Init(..) => {
          trace!("suspicious packet sent: {packet:?}");
          return;
        }

        _ => {}
      }

      broadcast_packet(packet).await;
    } else {
      let _ = self.write_sender.send(WriteMessage::Data(packet));
    }
  }
}
