// Copyright (C) Call of Nil contributors
// SPDX-License-Identifier: AGPL-3.0-only

use crate::app::App;
use crate::error::Error;
use crate::middleware::authorization::CurrentPlayer;
use crate::response::{EitherExt, from_err};
use crate::{bail_if_max_chars_exceeded, res};
use axum::extract::{Extension, Json, State};
use axum::response::Response;
use either::Either;
use itertools::Itertools;
use nil_core::player::{Player, PublicPlayer};
use nil_payload::request::player::*;
use nil_payload::response::player::*;

pub async fn exists(State(app): State<App>, Json(req): Json<PlayerExistsRequest>) -> Response {
  app
    .world(req.world, |world| world.has_player(&req.id))
    .await
    .map_left(|yes| res!(OK, PlayerExistsResponse(yes)))
    .into_inner()
}

pub async fn get_coords(
  State(app): State<App>,
  Json(req): Json<GetPlayerCoordsRequest>,
) -> Response {
  app
    .continent(req.world, |k| k.coords_of(req.id).collect_vec())
    .await
    .map_left(|coords| res!(OK, GetPlayerCoordsResponse(coords)))
    .into_inner()
}

pub async fn get_ids(State(app): State<App>, Json(req): Json<GetPlayerIdsRequest>) -> Response {
  app
    .player_manager(req.world, |pm| pm.player_ids().cloned().collect_vec())
    .await
    .map_left(|ids| res!(OK, GetPlayerIdsResponse(ids)))
    .into_inner()
}

pub async fn get_player(
  State(app): State<App>,
  Extension(player): Extension<CurrentPlayer>,
  Json(req): Json<GetPlayerRequest>,
) -> Response {
  app
    .player_manager(req.world, |pm| pm.player(&player).cloned())
    .await
    .try_map_left(|player| res!(OK, GetPlayerResponse(player)))
    .into_inner()
}

pub async fn get_public_player(
  State(app): State<App>,
  Json(req): Json<GetPublicPlayerRequest>,
) -> Response {
  app
    .player_manager(req.world, |pm| pm.player(&req.id).map(PublicPlayer::from))
    .await
    .try_map_left(|player| res!(OK, GetPublicPlayerResponse(player)))
    .into_inner()
}

pub async fn get_public_players(
  State(app): State<App>,
  Json(req): Json<GetPublicPlayersRequest>,
) -> Response {
  app
    .player_manager(req.world, |pm| {
      pm.players()
        .map(PublicPlayer::from)
        .collect_vec()
    })
    .await
    .map_left(|players| res!(OK, GetPublicPlayersResponse(players)))
    .into_inner()
}

pub async fn get_maintenance(
  State(app): State<App>,
  Extension(player): Extension<CurrentPlayer>,
  Json(req): Json<GetPlayerMaintenanceRequest>,
) -> Response {
  app
    .world(req.world, |world| world.get_player_maintenance(&player))
    .await
    .try_map_left(|maintenance| res!(OK, GetPlayerMaintenanceResponse(maintenance)))
    .into_inner()
}

pub async fn get_military(
  State(app): State<App>,
  Extension(player): Extension<CurrentPlayer>,
  Json(req): Json<GetPlayerMilitaryRequest>,
) -> Response {
  app
    .world(req.world, |world| world.get_player_military(&player))
    .await
    .try_map_left(|military| res!(OK, GetPlayerMilitaryResponse(military)))
    .into_inner()
}

pub async fn get_status(
  State(app): State<App>,
  Json(req): Json<GetPlayerStatusRequest>,
) -> Response {
  app
    .player_manager(req.world, |pm| pm.player(&req.id).map(Player::status))
    .await
    .try_map_left(|status| res!(OK, GetPlayerStatusResponse(status)))
    .into_inner()
}

pub async fn get_storage_capacity(
  State(app): State<App>,
  Extension(player): Extension<CurrentPlayer>,
  Json(req): Json<GetPlayerStorageCapacityRequest>,
) -> Response {
  app
    .world(req.world, |world| world.get_storage_capacity(player.0))
    .await
    .try_map_left(|capacity| res!(OK, GetPlayerStorageCapacityResponse(capacity)))
    .into_inner()
}

pub async fn get_worlds(
  State(app): State<App>,
  Json(req): Json<GetPlayerWorldsRequest>,
) -> Response {
  let mut worlds = Vec::new();

  for id in app.world_ids() {
    if let Either::Left(true) = app
      .player_manager(id, |pm| pm.has(&req.id))
      .await
    {
      worlds.push(id);
    }
  }

  res!(OK, GetPlayerWorldsResponse(worlds))
}

pub async fn set_status(
  State(app): State<App>,
  Extension(player): Extension<CurrentPlayer>,
  Json(req): Json<SetPlayerStatusRequest>,
) -> Response {
  app
    .world_mut(req.world, |world| {
      world.set_player_status(&player, req.status)
    })
    .await
    .try_map_left(|()| res!(OK))
    .into_inner()
}

pub async fn spawn(State(app): State<App>, Json(req): Json<SpawnPlayerRequest>) -> Response {
  bail_if_max_chars_exceeded!(req.options.id, 20);
  if app.server_kind().is_remote() {
    let password = req.world_password.as_ref();
    match app
      .database()
      .verify_game_password(req.world, password)
      .await
    {
      Ok(true) => {}
      Ok(false) => return from_err(Error::IncorrectWorldCredentials(req.world)),
      Err(err) => return from_err(err),
    }
  }

  app
    .world_blocking_mut(req.world, move |world| {
      world.spawn_player(Player::new(req.options))
    })
    .await
    .try_map_left(|()| res!(CREATED))
    .into_inner()
}
