/**
 *  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.mail.MessagingException;
import jakarta.ws.rs.FormParam;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
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.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.crypto.SecretKey;
import javax.naming.NamingException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.glassfish.jersey.media.multipart.FormDataParam;
import net.javacomm.database.DatabaseException;
import net.javacomm.database.WebdatabaseImpl;
import net.javacomm.database.entity.Config;
import net.javacomm.database.entity.EntityUser;
import net.javacomm.database.entity.Online;
import net.javacomm.database.entity.Outdated;
import net.javacomm.protocol.Benutzerstatus;
import net.javacomm.protocol.crypto.Crypto;
import net.javacomm.protocol.crypto.CryptoException;
import net.javacomm.share.Constants;
import net.javacomm.transfer.Modul;
import net.javacomm.transfer.TransferBenutzerkonto;
import net.javacomm.transfer.TransferConfig;
import net.javacomm.transfer.TransferOdxModulPermission;
import net.javacomm.transfer.TransferOutdated;
import net.javacomm.transfer.TransferRoomfilter;
import net.javacomm.transfer.TransferUser;



@Produces(MediaType.APPLICATION_JSON)
@Path("/administrator")
public class Administrator {

  final Logger log = LogManager.getLogger(Administrator.class);
  private WebdatabaseImpl database;

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



  /**
   * Lies die Konfigurationseinstellungen aus. Das zurückgegebene Transferobjekt
   * ist leer, aber {@code !=null}, wenn der Anwender kein Administrator ist.
   *
   *
   * @param userid
   *                 die Userid von einem Administrator
   * @param password
   *                 das zugehörige Passwort
   *
   * @return die Konfigurationseinstellungen
   * @throws SQLException
   */
  @POST
  @Path("/read/config")
  public TransferConfig readConfig(@FormDataParam(value = "userid") String userid,
      @FormDataParam(value = "password") String password) {

    if (!isAdministrator(userid, password)) return new TransferConfig();
    Config config;
    try {
      config = database.fetchConfig();
      TransferConfig transfer = new TransferConfig();
      transfer.setDomain(config.getDomain());
      transfer.setIsPublic(config.getIsPublic());
      transfer.setMailSmtpHost(config.getMailSmtpHost());
      transfer.setMailSmtpUser(config.getMailSmtpUser());
      transfer.setMailSmtpPort(config.getMailSmtpPort());
      transfer.setFlipToForum(config.getFlipToForum());
      transfer.setHours(config.getHours());
      transfer.setAccount(config.getAccount());
      return transfer;
    }
    catch (DatabaseException e) {
      log.error(e.getMessage());
      throw new WebApplicationException();
    }

  }



  /**
   * Lies eine Anwenderliste mit Kontoaktivitäten aus.
   *
   *
   * @param userid
   *                 die Userid von einem Administrator
   * @param password
   *                 das zugehörige Passwort
   * @return eine Anwenderliste mit Kontoaktivitäten
   */
  @POST
  @Path("/user")
  public List<TransferUser> getUserList(@FormDataParam(value = "userid") String userid,
      @FormDataParam(value = "password") String password) {

    ArrayList<TransferUser> liste = new ArrayList<>();
    if (!isAdministrator(userid, password)) return liste;

    List<EntityUser> userliste = database.readBenutzerAntraege();
    userliste.forEach((user) -> {
      TransferUser item = new TransferUser();
      int status = user.getBenutzerstatus();
      Benutzerstatus blubber = Benutzerstatus.toBenutzerstatus(status);
      item.setBenutzerstatus(blubber.getStatus());
      item.setMail(user.getMail());
      item.setUserid(user.getUid());
      liste.add(item);
    });
    return liste;
  }



  /**
   * Alle veralteten Programmversionsnummern werden gelesen.
   *
   * @return eine Liste veralterter Versionsnummern
   */
  @GET
  @Path("/read/outdated")
  public List<String> readOutdated() {
    return database.fetchOutdated();
  }



  /**
   * Alle verbotenen Nicknames werden gelesen.
   *
   * @return eine Liste verbotener Nicknames
   */
  @GET
  @Path("/read/nicknames")
  public List<String> readNicknames() {
    return database.fetchForbiddenNicknames();
  }



  @POST
  @Path("write/nicknames")
  @Produces(MediaType.TEXT_PLAIN)
  public boolean writeNicknames(@FormParam(value = "userid") String userid,
      @FormParam(value = "password") String password, @FormParam(value = "nicknames") Wrapper wrapper) {

    if (wrapper == null) return false;
    if (!isAdministrator(userid, password)) return false;
    List<String> nicknames = wrapper.getStringList();
    return database.updateForbiddenNicknames(nicknames);
  }



  @POST
  @Path("delete/nicknames")
  @Produces(MediaType.TEXT_PLAIN)
  public boolean deleteNicknames(@FormParam(value = "userid") String userid,
      @FormParam(value = "password") String password, @FormParam(value = "nicknames") Wrapper wrapper) {

    if (wrapper == null) return false;
    if (!isAdministrator(userid, password)) return false;
    return database.remainNicknames(wrapper.getStringList());
  }



  @POST
  @Path("delete/outdated")
  @Produces(MediaType.TEXT_PLAIN)
  public boolean deleteOutdated(@FormParam(value = "userid") String userid,
      @FormParam(value = "password") String password, @FormParam(value = "outdated") Wrapper wrapper) {

    if (wrapper == null) return false;
    if (!isAdministrator(userid, password)) return false;
    return database.remainOutdated(wrapper.getStringList());
  }



  @POST
  @Path("write/outdated")
  @Produces(MediaType.TEXT_PLAIN)
  public boolean writeOutdated(@FormParam(value = "userid") String userid,
      @FormParam(value = "password") String password, @FormParam(value = "outdated") Wrapper wrapper) {

    if (wrapper == null) return false;
    if (!isAdministrator(userid, password)) return false;
    List<TransferOutdated> outdatedlist = wrapper.getTransferOutdated();
    ArrayList<Outdated> entity = new ArrayList<>();
    for (TransferOutdated tmp : outdatedlist) {
      Outdated outdated = new Outdated();
      outdated.setProgrammversion(tmp.getProgrammversion());
      entity.add(outdated);
    }
    return database.updateOutdated(entity);
  }



  /**
   * Ist der Anwender ein Administrator?
   *
   *
   * @param userid
   *                 eine Userid
   * @param password
   *                 das zugehörige Passwort
   * @return {@code true}, ist ein Administrator
   */
  @POST
  @Produces(MediaType.TEXT_PLAIN)
  public boolean isAdministrator(@FormDataParam(value = "userid") String userid,
      @FormDataParam(value = "password") String password) {

    try {

//      log.info("uas der TB_ONLINE lesen=" + userid);

      Online online = database.fetchOnlineByUid(userid);
//      log.info("gelesen!!!!!!!!!!");
      if (online == null) {
        log.info(userid + " - online nicht gefunden");
        // HTTP Code senden 404 zurücksenden
        Response response = Response.status(Status.NOT_FOUND).entity(userid + " - ist nicht online").build();
        throw new WebApplicationException(response);
      }

//      log.info("session=" + online.getSession());
//      log.info("uid=" + online.getUid());
//      log.info("agent=" + online.getAgent());
//      log.info("aes=" + online.getAes());
//      log.info("password=" + password);

      // AES Schlüssel vom Administrator
      String aes = online.getAes();
      SecretKey secretkey = Crypto.getAESFromBase64(aes);

      String decrypt = Crypto.decryptAES(password, secretkey);

//      log.info("decryptPassword=" + decrypt);

      return database.isAdministrator(userid, decrypt);

    }
    catch (CryptoException e) {
      log.error(e.getMessage());
      Response response = Response.status(Status.BAD_REQUEST).build();
      throw new WebApplicationException(response);
    }
    catch (DatabaseException e) {
      log.error(e.getMessage());
      throw new WebApplicationException(e.getMessage());
    }

  }



  /**
   *
   *
   * @param userid
   *                  Userid von einem Admin
   * @param password
   *                  Password von einem Admin
   * @param userliste
   *                  die Benutzerdaten enthalten den neuen Status
   *
   * @return {@code true}, alle Änderungen wurden durchgeführt; {@code false}
   *         mindestens eine Änderung wurde nicht durhcgeführt
   */
  @POST
  @Path("/benutzerstatus")
  @Produces(MediaType.TEXT_PLAIN)
  public boolean writeBenutzerstatus(@FormDataParam(value = "userid") String userid,
      @FormDataParam(value = "password") String password,
      @FormDataParam(value = "userliste") Wrapper wrapper) {

    if (wrapper == null) return false;
    if (!isAdministrator(userid, password)) return false;
    boolean done = true;
    boolean result;
    for (TransferUser tmp : wrapper.getTransferUser()) {
      result = database
          .updateBenutzerstatus(Benutzerstatus.toBenutzerstatus(tmp.getBenutzerstatus()), tmp.getUserid());
      done = done && result;
    }
    return done;
  }



  /**
   * Neue Konfigurationseinstellungen werden gespeichert.
   *
   *
   * @param userid
   *                 Userid von einem Admin
   * @param password
   *                 Password von einem Admin
   * @param config
   *                 Transferobjekt mit Konfigurationseinstellungen
   * @return {@code true}, die Einstellungen wurden übernommen
   */
  @POST
  @Path("/write/config")
  @Produces(MediaType.TEXT_PLAIN)
  public boolean writeConfig(@FormParam(value = "userid") String userid,
      @FormParam(value = "password") String password, @FormParam(value = "config") TransferConfig config) {

    if (config == null) return false;
    if (!isAdministrator(userid, password)) return false;

    Config entityConfig = new Config();
    entityConfig.setDomain(config.getDomain());
    entityConfig.setIsPublic(config.getIsPublic());
    entityConfig.setMailSmtpHost(config.getMailSmtpHost());
    entityConfig.setMailSmtpPort(config.getMailSmtpPort());
    entityConfig.setMailSmtpUser(config.getMailSmtpUser());
    entityConfig.setFlipToForum(
        config.getFlipToForum() < 1 || config.getFlipToForum() > 1440 ? 150 : config.getFlipToForum()
    );
    entityConfig.setHours(
        config.getHours() < Constants.MIN_DURATION_TIME || config.getHours() > Constants.MAX_DURATION_TIME
            ? Constants.MAX_DURATION_TIME
            : config.getHours()
    );
    entityConfig.setAccount(
        entityConfig.getAccount() < Constants.MIN_ACCOUNT_TIME ? Constants.MIN_ACCOUNT_TIME
            : entityConfig.getAccount()
    );
    try {
      database.updateConfig(entityConfig);
    }
    catch (DatabaseException e) {
      log.error(e.getMessage(), e);
      return false;
    }
    return true;
  }



  /**
   * Alle Raumfilter werden zurückgegeben.
   *
   *
   * @param userid
   *                 die Userid von einem Admin
   * @param password
   *                 das zugehörige Passwort
   * @return die Filterliste
   */
  @POST
  @Path("/roomfilter")
  public List<TransferRoomfilter> readRoomfilter(@FormDataParam(value = "userid") String userid,
      @FormDataParam(value = "password") String password) {

    ArrayList<TransferRoomfilter> filterliste = new ArrayList<>();
    if (!isAdministrator(userid, password)) return filterliste;
    List<String> result = database.fetchForbiddenNames();
    result.forEach((forbidden) -> {
      TransferRoomfilter roomfilter = new TransferRoomfilter();
      roomfilter.setRoomfilter(forbidden);
      filterliste.add(roomfilter);
    });
    return filterliste;
  }



  @POST
  @Path("/roomfilter")
  @Produces(MediaType.TEXT_PLAIN)
  public boolean writeRoomfilter(@FormParam(value = "userid") String userid,
      @FormParam(value = "password") String password, @FormParam(value = "roomfilter") Wrapper wrapper) {

    if (!isAdministrator(userid, password)) return false;
    database.clearForbiddenRooms();
    List<TransferRoomfilter> filterliste = wrapper.getTransferRoomfilter();
    boolean done = true;
    for (TransferRoomfilter tmp : filterliste) {
      done = done && database.addRoomfilter(tmp.getRoomfilter());
    }
    return done;
  }



  /**
   * Sende eine Testmail.
   * 
   * @param userid
   *                       diese userid ist ein Administrator
   * @param password
   *                       dieses Passwort gehört zum Administrator
   * @param transferConfig
   *                       enthält Attribute für Mailing
   * 
   * @return an diese Mailadresse wurde gesendet; der Rückgabewert kann leer sein,
   *         wenn der Anfrager kein Administrator war.
   */
  @POST
  @Path("/testmail")
  @Produces(MediaType.TEXT_PLAIN)
  public Response writeTestmail(@FormParam(value = "userid") String userid,
      @FormParam(value = "password") String password,
      @FormParam(value = "testmail") TransferConfig transferConfig) {

    if (!isAdministrator(userid, password)) {
      Response forbidden = Response.status(Status.FORBIDDEN).build();
      new WebApplicationException(forbidden);
    }
    Config configEntity = new Config();
    configEntity.setDomain(transferConfig.getDomain());
    configEntity.setIsPublic(transferConfig.getIsPublic());
    configEntity.setMailSmtpHost(transferConfig.getMailSmtpHost());
    configEntity.setMailSmtpPort(transferConfig.getMailSmtpPort());
    configEntity.setMailSmtpUser(transferConfig.getMailSmtpUser());
    try {
      String adminmail = database.testMail(configEntity);
      Response ok = Response.status(Status.OK).type(MediaType.TEXT_PLAIN).entity(adminmail).build();
      return ok;
    }
    catch (NamingException | MessagingException e) {
      log.error(e.getMessage());
      Response response = Response.status(Status.INTERNAL_SERVER_ERROR).type(MediaType.TEXT_PLAIN)
          .entity(e.getMessage()).build();
      throw new WebApplicationException(response);
    }
  }



  @POST
  @Path("/read/accounts")
  @Produces(MediaType.APPLICATION_JSON)
  public List<TransferBenutzerkonto> readBenutzerkonten(@FormDataParam(value = "admin") String admin,
      @FormDataParam(value = "password") String password) {

    ArrayList<TransferBenutzerkonto> accounts = new ArrayList<TransferBenutzerkonto>();
    if (!isAdministrator(admin, password)) return accounts;

    try {
      List<EntityUser> result = database.fetchUsers();
      result.forEach(entityUser -> {

        TransferBenutzerkonto transferkonto = new TransferBenutzerkonto();
        transferkonto.setLdelete(entityUser.isLdelete());
        transferkonto.setMail(entityUser.getMail());
        transferkonto.setNickname(entityUser.getNickname());
        transferkonto.setTime(entityUser.getTime());
        transferkonto.setUid(entityUser.getUid());

        accounts.add(transferkonto);

      });

    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new WebApplicationException(e);
    }
    return accounts;
  }



  /**
   * Frage die ODX-Berechtigung für alle Anwender ab.
   *
   *
   * @param userid
   *               der Admin
   * @return
   */
  @POST
  @Path("/read/modul/odx/permission")
  @Produces(MediaType.APPLICATION_JSON)
  public ArrayList<TransferOdxModulPermission> readOdxPermission(
      @FormDataParam(value = "userid") String userid, @FormDataParam(value = "password") String password) {

    ArrayList<TransferOdxModulPermission> odxUsers = new ArrayList<>();
    if (!isAdministrator(userid, password)) return odxUsers;
    return database.fetchOdxuser();
  }



  /**
   * Die ODX-Berechtigung wird erteilt oder entzogen.
   *
   *
   * @param userid
   *                 der Admin
   * @param password
   *                 Admins password
   * @param odxmodul
   *                 Anwenderattribute über seine Berechtigung
   * @return
   */
  @POST
  @Path("/write/modul/odx/permission")
  @Produces(MediaType.APPLICATION_JSON)
  public void writeOdxPermission(@FormDataParam(value = "userid") String userid,
      @FormDataParam(value = "password") String password,
      @FormDataParam(value = "odxmodul") TransferOdxModulPermission odxmodul) {

    if (!isAdministrator(userid, password)) return;

    if (odxmodul.isOdxAllow()) {
      database.grantModule(odxmodul.getUserid(), Modul.ODX.getModule());
      // Die Modulattribute für den Anwender setzen
      database.insertUserOdx(odxmodul.getUserid());
    }
    else {
      database.revokeModule(odxmodul.getUserid(), Modul.ODX.getModule());
      // die Modulattribute für den Anwender entziehen.
      database.deleteUserOdx(odxmodul.getUserid());

    }

  }



  /**
   * Ein Benutzeraccount wird logisch gelöscht oder zurückgeholt.
   *
   *
   * @param adminid
   *                 nur ein Administrator darf die Aktion durchführen
   * @param password
   *                 Admins password
   * @param userid
   *                 der Anwender, dessen Konto gelöscht oder zurückgeholt wird
   * @param deleted
   *                 {@code true}, das Konto wird gelöscht
   *
   * @return {@code deleted}, wenn der Aufruf erfolgreich war
   */
  @POST
  @Path("/delete/logically")
  @Produces(MediaType.TEXT_PLAIN)
  public boolean logicallyDelete(@FormDataParam(value = "admin") String adminid,
      @FormDataParam(value = "password") String password, @FormDataParam(value = "userid") String userid,
      @FormDataParam(value = "deleted") boolean deleted) {

    if (!isAdministrator(adminid, password)) return deleted;
    try {
      database.deleteLogicUser(userid, deleted);
    }
    catch (SQLException e) {
      log.error(e.getMessage());
      throw new WebApplicationException();
    }
    return deleted;
  }

}
