import app/clients/new_user_client
import app/web
import gleam/bit_array
import gleam/bytes_tree
import gleam/erlang/process
import gleam/http/cookie
import gleam/http/request.{type Request}
import gleam/http/response.{type Response}
import gleam/int
import gleam/io
import gleam/json
import gleam/option.{None, Some}
import gleam/otp/actor
import gleam/result
import gleam/string
import login
import mist.{type Connection, type ResponseData}
import player
import player_manager
import session_manager
import socket_manager

type JoinAs {
  JoinAsHost
  JoinAsPlayer
}

pub fn handle_request(
  req: Request(Connection),
  ctx: web.Context,
) -> Response(ResponseData) {
  io.println("Got request: " <> req.path)
  case request.path_segments(req) {
    [] -> serve_wasm(ctx, "index.html", "text/html")
    ["static", "frontend.js"] ->
      serve_wasm(ctx, "frontend.js", "text/javascript")
    ["static", "frontend_bg.wasm"] ->
      serve_wasm(ctx, "frontend_bg.wasm", "application/wasm")
    ["ws"] -> serve_websocket(ctx, req)
    ["login", "host"] -> server_login(ctx, req, JoinAsHost)
    ["login", "join"] -> server_login(ctx, req, JoinAsPlayer)
    _ -> response.set_body(response.new(404), mist.Bytes(bytes_tree.new()))
  }
}

fn server_login(
  ctx: web.Context,
  req: Request(Connection),
  join_type: JoinAs,
) -> Response(ResponseData) {
  let assert Ok(req) = mist.read_body(req, 1000)
  let assert Ok(body) = bit_array.to_string(req.body)

  let assert Ok(login_req) = json.parse(body, login.login_request_decoder())
  let assert Ok(#(user_id, token)) =
    player_manager.add_player(
      ctx.players,
      login_req.username,
      login_req.game_code,
    )

  let _ = case join_type {
    JoinAsHost -> {
      let assert Ok(_) =
        session_manager.create_new_game(
          ctx.sessions,
          login_req.game_code,
          user_id,
        )

      Nil
    }
    JoinAsPlayer -> {
      let assert Ok(_) =
        session_manager.add_player_to_game(
          ctx.sessions,
          login_req.game_code,
          player.Player(
            name: login_req.username,
            id: user_id,
            score: 0,
            buzz_in_time: None,
          ),
        )
      Nil
    }
  }

  let join_type_name = case join_type {
    JoinAsHost -> "host"
    JoinAsPlayer -> "player"
  }
  let cookie_attr =
    cookie.Attributes(..cookie.defaults(req.scheme), http_only: False)

  response.new(200)
  |> response.set_cookie("play_of_the_game/token", token, cookie_attr)
  |> response.set_cookie(
    "play_of_the_game/client_type",
    join_type_name,
    cookie_attr,
  )
  |> response.set_cookie(
    "play_of_the_game/username",
    login_req.username,
    cookie_attr,
  )
  |> response.set_cookie(
    "play_of_the_game/user_id",
    int.to_string(user_id),
    cookie_attr,
  )
  |> response.set_cookie(
    "play_of_the_game/game_code",
    login_req.game_code,
    cookie_attr,
  )
  |> response.set_body(mist.Bytes(bytes_tree.new()))
}

fn serve_wasm(
  ctx: web.Context,
  file: String,
  mimetype: String,
) -> Response(ResponseData) {
  let wasm_path = string.join([ctx.config.static_path, file], "/")
  mist.send_file(wasm_path, offset: 0, limit: None)
  |> result.map(fn(file) {
    response.new(200)
    |> response.prepend_header("content-type", mimetype)
    |> response.set_body(file)
  })
  |> result.lazy_unwrap(fn() {
    response.new(404)
    |> response.set_body(mist.Bytes(bytes_tree.new()))
  })
}

fn serve_websocket(
  ctx: web.Context,
  req: Request(Connection),
) -> Response(ResponseData) {
  mist.websocket(
    request: req,
    on_init: fn(_conn) {
      let self = process.new_subject()

      let selector =
        process.new_selector()
        |> process.selecting(self, fn(x) { x })

      let id = socket_manager.add_socket_subj(ctx.socket_manager, self)

      let state =
        web.SocketState(
          ctx:,
          id:,
          subject: self,
          state: web.NewUser(new_user_client.NewUserState),
        )
      #(state, Some(selector))
    },
    on_close: fn(state) {
      io.println("Shutting down socket" <> int.to_string(state.id))
      socket_manager.remove_socket_subj(state.ctx.socket_manager, state.subject)
    },
    handler: handle_ws_message,
  )
}

fn handle_ws_message(
  state: web.SocketState,
  conn,
  message: mist.WebsocketMessage(String),
) {
  io.println("Got WS message")
  case message {
    mist.Text(msg) -> {
      echo msg

      let #(state, resp) = web.handle_client_msg(state, msg)

      echo resp
      let assert Ok(_) = mist.send_text_frame(conn, resp)

      actor.continue(state)
    }
    mist.Binary(_) -> {
      actor.continue(state)
    }
    mist.Custom(resp) -> {
      echo resp
      let assert Ok(_) = mist.send_text_frame(conn, resp)
      actor.continue(state)
    }
    mist.Closed | mist.Shutdown -> actor.Stop(process.Normal)
  }
}
