/**
 *  Copyright © 2025, Luis Andrés Lange <https://javacomm.net>
 *
 *  Previously released under Apache License 2.0; now licensed under MPL 2.0.
 *
 *  This Source Code Form is subject to the terms of the Mozilla Public
 *  License, v. 2.0. If a copy of the MPL was not distributed with this
 *  file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 *  ----------------------------------------------------------------------------
 *
 *  Exhibit B - "Incompatible With Secondary Licenses" Notice
 *
 *  This Source Code Form is "Incompatible With Secondary Licenses",
 *  as defined by the Mozilla Public License, v. 2.0.
 *
 *  In short:
 *  - This file may be used, modified, and distributed under MPL 2.0 only.
 *  - It may NOT be relicensed under GPL, LGPL, AGPL, or any other Secondary License.
 *
 *  Rationale:
 *  - Ensures that the code remains MPL-2.0.
 *  - Avoids legal conflicts with GPL-licensed libraries (e.g., VideoLAN).
 *  - Maximizes usability for commercial and security-critical applications.
 *
 */
package net.javacomm.restserver;

import jakarta.ws.rs.FormParam;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.HEAD;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import java.security.PrivateKey;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.javacomm.database.DatabaseException;
import net.javacomm.database.WebdatabaseImpl;
import net.javacomm.database.entity.EntityUser;
import net.javacomm.database.entity.UserModule;
import net.javacomm.facade.DatabaseService;
import net.javacomm.multilingual.MultilingualString;
import net.javacomm.multilingual.schema.ISO639;
import net.javacomm.multilingual.schema.KEY;
import net.javacomm.protocol.Language;
import net.javacomm.protocol.SIGNIN;
import net.javacomm.protocol.Token;
import net.javacomm.protocol.crypto.Crypto;
import net.javacomm.server.Mailsender;
import net.javacomm.transfer.Helado;
import net.javacomm.transfer.Modul;
import net.javacomm.transfer.Sprachguete;
import net.javacomm.transfer.TransferUser;



@Produces(MediaType.TEXT_PLAIN)
@Path("/user")
public class User {

  private final static Logger log = LogManager.getLogger(User.class);
  private DatabaseService database;

  public User() {
    database = WebdatabaseImpl.getInstance();
  }



  /**
   * Hat der Anwender eine Zugriffsberechtigung für ODX?
   *
   *
   * @param userid
   *               ein Anwender
   *
   * @return {@code true}, der Anwender ist berechtigt
   *
   */
  @GET
  @Path("/read/modul/odx")
  @Produces(MediaType.TEXT_PLAIN)
  public boolean isODX(@QueryParam(value = "userid") String userid) {
    List<UserModule> list = database.fetchModules(userid);
    for (UserModule usermodule : list) {
      Modul module = Modul.toModule(usermodule.getModule());
      if (module == Modul.ODX) return true;
    }
    return false;
  }



  /**
   * KEEPALIVE ist eine Zeitscheibe und wird als Referenz für das Schließen der
   * Clientverbindung herangezogen.
   * 
   * @param userid
   *               dieser Anwender
   * @return {@code true}, der Zeitstempel konnte für den Anwender aktualisiert
   *         werden
   * 
   * @throws SQLException
   *                      generischer SQL-Fehler
   * 
   * @deprecated As of Version 260501-PRE2. Use {@link #keepAliveBody(String)}
   *             instead.
   * 
   */
  @PUT
  @Path("/write/keepalive/{userid}")
  @Produces(MediaType.TEXT_PLAIN)
  @Deprecated
  public boolean keepAlive(@PathParam(value = "userid") String userid) {
    try {
      return database.updateKEEPALIVE(userid);
    }
    catch (DatabaseException e) {
      log.error(e.getMessage());
      throw new WebApplicationException();
    }
  }



  /**
   * KEEPALIVE ist eine Zeitscheibe und wird als Referenz für das Schließen der
   * Clientverbindung herangezogen.
   * 
   * @param userid
   *               dieser Anwender
   * @return {@code true}, der Zeitstempel konnte für den Anwender aktualisiert
   *         werden
   * 
   * @throws SQLException
   *                      generischer SQL-Fehler
   * 
   */
  @PUT
  @Path("/write/keepalive")
  @Produces(MediaType.TEXT_PLAIN)
  public boolean keepAliveBody(String userid) {
    try {
      return database.updateKEEPALIVE(userid);
    }
    catch (DatabaseException e) {
      log.error(e.getMessage());
      throw new WebApplicationException();
    }
  }



  /**
   * Alle Attribute für einen Anwender aus der Tabelle TB_USER werden gelesen. Der
   * Rückgabewert kann leer sein, wenn kein Anwender gefunden wurde.
   *
   * @param userid
   *               ein Anwender
   * @return die Attribute sind in genau einem Listenelement
   * 
   */
  @Deprecated
  @Path("/read/data/{userid}")
  @GET
  @Produces(MediaType.APPLICATION_JSON)
  public List<TransferUser> readData(@PathParam(value = "userid") String userid) {

    net.javacomm.database.entity.EntityUser entityUser = database.fetchUser(userid);
    if (entityUser == null) {
      Response response = Response.status(Status.NOT_FOUND).type(MediaType.TEXT_PLAIN)
          .entity(userid + " - nicht gefunden").build();
      throw new WebApplicationException(response);

    }

    ArrayList<TransferUser> list = new ArrayList<>();
    TransferUser transferuser = new TransferUser();
    transferuser.setUserid(entityUser.getUid());
    transferuser.setBenutzerstatus(entityUser.getBenutzerstatus());
    transferuser.setMail(entityUser.getMail());
    transferuser.setChatdenied(entityUser.isChatdenied());
    transferuser.setChattooltip(entityUser.isChattooltip());
    transferuser.setVolume(entityUser.getVolume());
    transferuser.setPlayervolume(entityUser.getPlayervolume());
    transferuser.setOncall(entityUser.isOncall());
    transferuser.setTelefonpuffer(Sprachguete.toSprachguete(entityUser.getTelefonpuffer()));
    transferuser.setHelado(Helado.toHelado(entityUser.getEis()));
    transferuser.setSpeaker(entityUser.getSpeaker());
    transferuser.setMikrofon(entityUser.getMikrofon());
    transferuser.setOnrekord(entityUser.isOnrekord());
    transferuser.setOnprojektor(entityUser.isOnprojektor());
    transferuser.setIptvvolume(entityUser.getIptvvolume());
    transferuser.setLanguage(entityUser.getLanguage());
    transferuser.setChatdownloaddir(entityUser.getChatDownloadDir());
    transferuser.setChatuploaddir(entityUser.getChatUploadDir());
    transferuser.setLdelete(entityUser.isLdelete());
    transferuser.setNickname(entityUser.getNickname());
    list.add(transferuser);
    return list;
  }



  /**
   * Alle Attribute für einen Anwender aus der Tabelle TB_USER werden gelesen. Der
   * Rückgabewert kann leer sein, wenn kein Anwender gefunden wurde.
   *
   * @param userid
   *               ein Anwender
   * @return die Attribute sind in genau einem Listenelement
   */
  @Path("/read/data")
  @GET
  @Produces(MediaType.APPLICATION_JSON)
  public List<TransferUser> readDataHeader(@HeaderParam(value = "userid") String userid) {

    net.javacomm.database.entity.EntityUser entityUser = database.fetchUser(userid);
    if (entityUser == null) {
      Response response = Response.status(Status.NOT_FOUND).type(MediaType.TEXT_PLAIN)
          .entity(userid + " - nicht gefunden").build();
      throw new WebApplicationException(response);

    }

    ArrayList<TransferUser> list = new ArrayList<>();
    TransferUser transferuser = new TransferUser();
    transferuser.setUserid(entityUser.getUid());
    transferuser.setBenutzerstatus(entityUser.getBenutzerstatus());
    transferuser.setMail(entityUser.getMail());
    transferuser.setChatdenied(entityUser.isChatdenied());
    transferuser.setChattooltip(entityUser.isChattooltip());
    transferuser.setVolume(entityUser.getVolume());
    transferuser.setPlayervolume(entityUser.getPlayervolume());
    transferuser.setOncall(entityUser.isOncall());
    transferuser.setTelefonpuffer(Sprachguete.toSprachguete(entityUser.getTelefonpuffer()));
    transferuser.setHelado(Helado.toHelado(entityUser.getEis()));
    transferuser.setSpeaker(entityUser.getSpeaker());
    transferuser.setMikrofon(entityUser.getMikrofon());
    transferuser.setOnrekord(entityUser.isOnrekord());
    transferuser.setOnprojektor(entityUser.isOnprojektor());
    transferuser.setIptvvolume(entityUser.getIptvvolume());
    transferuser.setLanguage(entityUser.getLanguage());
    transferuser.setChatdownloaddir(entityUser.getChatDownloadDir());
    transferuser.setChatuploaddir(entityUser.getChatUploadDir());
    transferuser.setLdelete(entityUser.isLdelete());
    transferuser.setNickname(entityUser.getNickname());
    list.add(transferuser);
    return list;
  }



  @Path("/read/playervolume")
  @POST
  public int readPlayervolume(@FormParam(value = "userid") String userid) {
    return database.fetchPlayervolume(userid);
  }



  @Path("/read/speaker")
  @GET
  public String readSpeaker(@QueryParam(value = "email") String email) {

    if (email == null) return "";
    return database.fetchSpeaker(email);
  }



  /**
   * Das Einmal-Token wird bei einem USRLOGIN und UPDATE benötigt. Es ist Teil vom
   * IDENTITY Attribut.
   * 
   * @return dieses Einmal-Token
   */
  @Path("/read/token")
  @POST
  @Produces(MediaType.TEXT_PLAIN)
  public String readToken() {
    String uuid = UUID.randomUUID().toString();
    database.insertToken(uuid);
    return uuid;
  }



  /**
   * Der Anwender fordert ein neues Password an.
   * 
   * @param identity
   *                 die Benutzerdaten sind RSA verschlüsselt und wurden Base64
   *                 codiert übergeben
   * 
   * @return
   */
  @PUT
  @Path("/renew/password/{language}")
  @Produces(MediaType.TEXT_PLAIN)
  public Response renewPassword(@PathParam("language") String language, String identity) {
    try {
      String rsa = database.fetchRsaPrivateKey();
      PrivateKey privateKey = Crypto.loadPrivateRSAKey(rsa);
      String klartext = Crypto.decryptRSA(identity, privateKey);
      Token token = Token.toToken(klartext);

      // Ist die Email vorhanden

      EntityUser entityUser = database.fetchUserByMail(token.getEMail());

      if (entityUser == null) {
        // Anwender ist nicht registriert
        // 404 NOT_FOUND

        MultilingualString text = new MultilingualString(
            KEY.STRING_KEIN_REGISTRIERTER_ANWENDER, ISO639.fromValue(language)
        );

        Response response = Response.status(Status.NOT_FOUND).type(MediaType.TEXT_PLAIN)
            .entity(text.toString()).build();
        return response;
      }

      // Anwender ist registriert

//      log.info("CONFIRMATION_FLAG=" + entityUser.getConfirmation_flag());
      if (entityUser.getConfirmation_flag() == 1) {
        // 403 FORBIDDEN
        // vorübergehend gesperrt

        MultilingualString text = new MultilingualString(
            KEY.STRING_KONTO_GESPERRT, ISO639.fromValue(language)
        );

        Response response = Response.status(Status.FORBIDDEN).type(MediaType.TEXT_PLAIN)
            .entity(text.toString()).build();
        return response;
      }

      // Password darf erneuert werden

      // Passwort an die Mailadresse senden

      // neues Neues_PASSWORT und CONFIRMATION_KEY werden in der Datenbank gesetzt.
      SIGNIN signin = database.createConfirmationKey(entityUser.getUid());

      // kann einen SocketTimeout verursachen
      // die Zeit wurde in JerseyClientFactory auf 9 Sekunden gesetzt

      new Thread(() -> {
        Mailsender.sendMail(
            entityUser.getMail(), signin.getNeuesPassword(), entityUser.getLanguage().toString(),
            signin.getConfirmationKey()
        );
      }).start();

      database.updateConfirmationFlag(entityUser.getUid(), entityUser.getMail(), 1);

      MultilingualString text = new MultilingualString(KEY.SERVER_NEUES_PASSWORT, ISO639.fromValue(language));

      return Response.ok().entity(text.toString()).build();
    }
    catch (Exception e) {
      // 400, Identitätsstring ist fehlerhaft
      MultilingualString text = new MultilingualString(
          KEY.STRING_NOT_ENCRYPTING_PROPERLY, ISO639.fromValue(language)
      );
      Response response = Response.status(Status.BAD_REQUEST).type(MediaType.TEXT_PLAIN)
          .entity(e.getMessage()).entity(text.toString()).build();
      throw new WebApplicationException(response);
    }
  }



  /**
   * Sollen Chatanfragen abgelehnt werden?
   *
   *
   * @param userid
   *                   die Benutzer-ID vom Schreiber
   * @param chatdenied
   *                   {@code true}, Chatanfragen werden abgelehnt
   */
  @Path("/write/chatdenied")
  @POST
  public void writeChatdenied(@FormParam(value = "userid") String userid,
      @FormParam(value = "chatdenied") boolean chatdenied) {
    database.updateChatdenied(userid, chatdenied);
  }



  @Path("/write/chattooltip")
  @HEAD
  public void writeChattooltip(@QueryParam(value = "chattooltip") boolean tooltip,
      @QueryParam(value = "userid") String userid) {
    database.updateChattooltip(tooltip, userid);
  }



  @Path("/write/eis")
  @POST
  public void writeEis(@FormParam(value = "userid") String userid, @FormParam(value = "eis") Helado helado) {

    database.updateEis(userid, helado.getHelado());
  }



  /**
   * Globale Benutzerattribute werden gespeichert.
   *
   *
   */
  @Path("/write/global/attributes")
  @POST
  public void writeGlobalAttributes(@FormParam(value = "userid") String userid,
      @FormParam(value = "playervolume") int playervolume, @FormParam(value = "iptvvolume") int iptvvolume,
      @FormParam(value = "eis") Helado helado, @FormParam(value = "language") String language,
      @FormParam(value = "chatDownloadDir") String chatDownloadDir,
      @FormParam(value = "chatUploadDir") String chatUploadDir) {

    database.updateGlobalAttributes(
        userid, playervolume, iptvvolume, helado.getHelado(), language, chatDownloadDir, chatUploadDir
    );

  }



  /**
   * Der Anwender legt seine Spracheinstellung fest.
   * 
   * @param language
   *                 diese Sprache
   * @param userid
   *                 dieser Anwender
   * @return {@code Language.de, Language.en Language.es}, wenn die
   *         Spracheinstellung übernommen wurde
   */
  @PUT
  @Path("/write/language/{language}/{userid}")
  @Produces(MediaType.TEXT_PLAIN)
  public Language writeLanguage(@PathParam(value = "language") Language language,
      @PathParam(value = "userid") String userid) {

    try {
      return database.updateLanguage(language, userid);
    }
    catch (DatabaseException e) {
      log.error(e.getMessage());
      throw new WebApplicationException();
    }
  }



  /**
   * Der Anwender teilt mit, dass sein Projektor läuft
   *
   * @param projektor
   * @return {@code true}, der Projektor ist in Betrieb
   */
  @Path("/write/onprojektor")
  @POST
  public boolean writeOnprojektor(@FormParam(value = "projektor") TransferUser projektor) {

    EntityUser user = new EntityUser();
    user.setUid(projektor.getUserid());
    user.setOnprojektor(projektor.getOnprojektor());
    boolean done = database.updateOnprojektor(user);
    return done;
  }



  /**
   * Der Anwender teilt mit, ob er Bildübertragungen annimmt.
   *
   * @param rekorder
   * @return {@code true}, der Anwender nimmt Bildübertragungen entgegen
   */
  @Path("/write/onrekorder")
  @POST
  public boolean writeOnrekorder(@FormParam(value = "rekorder") TransferUser rekorder) {

    EntityUser user = new EntityUser();
    user.setUid(rekorder.getUserid());
    user.setOnrekord(rekorder.getOnrekord());
    boolean done = database.updateOnrekorder(user);
    return done;
  }



  @Path("/write/playervolume")
  @POST
  public boolean writePlayervolume(@FormParam(value = "userid") String userid,
      @FormParam(value = "playervolume") int playervolume) {

    if (0 > playervolume || playervolume > 102) return false;
    return database.updatePlayervolume(userid, playervolume);
  }



  /**
   * Der Anwender teilt seine Einwilligung oder Ablehnung für den
   * Bildschirmempfang mit.
   *
   * @param rekorder
   * @return {@code true}, die Daten konnten gespeichert werden.
   */
  @Path("/write/rekorder")
  @POST
  public boolean writeRekorder(@FormParam(value = "rekorder") TransferUser rekorder) {

    EntityUser user = new EntityUser();
    user.setUid(rekorder.getUserid());
    user.setOnrekord(rekorder.getOnrekord());
    boolean done = database.updateRekorder(user);
    return done;
  }



  /**
   * Nur die Benutzerdaten für das Telefon werden gespeichert.
   *
   *
   * @param telefon
   *                die Telefondaten wie Telefonport, Sprachgüte und Userid
   * @return {@code true}, die Daten konnten gespeichert werden.
   */
  @Path("/write/telefon")
  @POST
  public boolean writeTelefon(@FormParam(value = "telefon") TransferUser telefon) {

    net.javacomm.database.entity.EntityUser user = new net.javacomm.database.entity.EntityUser();
    user.setUid(telefon.getUserid());
    user.setTelefonpuffer(telefon.getTelefonpuffer().getPuffer());
    user.setMikrofon(telefon.getMikrofon());
    user.setSpeaker(telefon.getSpeaker());
    boolean done = database.updateTelefon(user);
    return done;
  }



  @Path("/volume")
  @POST
  public void writeTelefonvolume(@FormParam(value = "userid") String userid,
      @FormParam(value = "volume") int volume) {

    database.updatePhoneVolume(userid, volume);
  }

}
