import app/clients/host_client
import app/clients/new_user_client
import app/clients/player_client
import config
import gleam/erlang/process
import gleam/float
import gleam/int
import gleam/io
import gleam/json
import player_manager
import session_manager
import socket_manager

pub type Error {
  InvalidToken
}

pub type Context {
  Context(
    config: config.Config,
    sessions: session_manager.SessionManager,
    players: player_manager.PlayerManager,
    socket_manager: socket_manager.SocketManager(String),
  )
}

pub type ClientState {
  Host(host_client.HostState)
  Player(player_client.PlayerState)
  NewUser(new_user_client.NewUserState)
}

pub type SocketState {
  SocketState(
    ctx: Context,
    id: Int,
    subject: process.Subject(String),
    state: ClientState,
  )
}

pub fn handle_client_msg(
  socket_state: SocketState,
  msg: String,
) -> #(SocketState, String) {
  case socket_state.state {
    Host(state) -> handle_host(socket_state, state, msg)
    NewUser(state) -> handle_new_user(socket_state, state, msg)
    Player(state) -> handle_player(socket_state, state, msg)
  }
}

fn handle_player(
  socket_state: SocketState,
  player_state: player_client.PlayerState,
  msg: String,
) -> #(SocketState, String) {
  let assert Ok(msg) =
    json.parse(msg, player_client.player_client_messages_decoder())

  case msg {
    player_client.BuzzIn(time) -> {
      io.println(
        "Got buzz in from "
        <> int.to_string(player_state.user_id)
        <> " at "
        <> float.to_string(time),
      )

      let assert Ok(_) =
        session_manager.buzz_in_player(
          socket_state.ctx.sessions,
          player_state.game_id,
          player_state.user_id,
          time,
        )

      #(
        socket_state,
        json.to_string(player_client.encode_player_server_messages(
          player_client.Ack,
        )),
      )
    }
  }
}

fn handle_new_user(
  socket_state: SocketState,
  _new_user_state: new_user_client.NewUserState,
  msg: String,
) -> #(SocketState, String) {
  let assert Ok(msg) =
    json.parse(msg, new_user_client.new_user_client_messages_decoder())

  let #(socket_state, new_user_msg) = case msg {
    new_user_client.JoinAsHost(token, user_id, game_id) -> {
      let assert Ok(_) =
        player_manager.attack_socket_to_player(
          socket_state.ctx.players,
          user_id,
          socket_state.id,
          token,
        )

      #(
        SocketState(
          ..socket_state,
          state: Host(host_client.HostState(user_id:, game_id:)),
        ),
        new_user_client.HostRegisterResponse,
      )
    }
    new_user_client.JoinAsPlayer(token, user_id, game_id) -> {
      let assert Ok(_) =
        player_manager.attack_socket_to_player(
          socket_state.ctx.players,
          user_id,
          socket_state.id,
          token,
        )

      #(
        SocketState(
          ..socket_state,
          state: Player(player_client.PlayerState(user_id:, game_id:)),
        ),
        new_user_client.RegisterResponse,
      )
    }
  }

  #(
    socket_state,
    json.to_string(new_user_client.encode_new_user_server_messages(new_user_msg)),
  )
}

fn handle_host(
  socket_state: SocketState,
  host_state: host_client.HostState,
  msg: String,
) -> #(SocketState, String) {
  let assert Ok(msg) =
    json.parse(msg, host_client.host_client_messages_decoder())

  let #(socket_state, resp) = case msg {
    host_client.ResetAllBuzers -> {
      let assert Ok(_) =
        session_manager.reset_all_buzzers(
          socket_state.ctx.sessions,
          host_state.game_id,
        )
      #(socket_state, host_client.Ack)
    }
    host_client.ResetPlayerBuzzer(id) -> {
      let assert Ok(_) =
        session_manager.reset_player_buzzer(
          socket_state.ctx.sessions,
          host_state.game_id,
          id,
        )

      #(socket_state, host_client.Ack)
    }
    host_client.UpdateScore(id, score) -> {
      let assert Ok(_) =
        session_manager.updater_player_score(
          socket_state.ctx.sessions,
          host_state.game_id,
          id,
          score,
        )

      #(socket_state, host_client.Ack)
    }
    host_client.EndGame -> {
      let assert Ok(_) =
        session_manager.end_game(socket_state.ctx.sessions, host_state.game_id)

      #(socket_state, host_client.Ack)
    }
  }

  let resp = json.to_string(host_client.encode_host_server_messages(resp))

  #(socket_state, resp)
}
