/**
 *  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.Consumes;
import jakarta.ws.rs.FormParam;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.HeaderParam;
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.MultivaluedMap;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
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.protocol.CONFERENCE;
import net.javacomm.transfer.TransferKonferenzraum;
import net.javacomm.transfer.TransferOrganizeConferenceCall;
import net.javacomm.transfer.TransferTelko;



@Path("/telko")
public class Telko {

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

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



  /**
   * Erstelle eine Telefonkonferenz.
   *
   *
   * @param userid
   *                      der Organisator der Telefonkonferenz
   * @param konferenzname
   *                      der Konferenzname
   */
  @Produces(MediaType.TEXT_PLAIN)
  @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
  @Path("/erstellen")
  @POST
  public boolean createTelko(@FormParam("pseudoid") String pseudoid,
      @FormParam("konferenzname") String konferenzname, @FormParam("texto") String texto) {

    net.javacomm.database.entity.Telko result;
    try {
      String userid = database.fetchUserByPseudoid(pseudoid).get();
      result = database.createTelefonkonferenz(userid, konferenzname, texto);
      return result != null;
    }
    catch (DatabaseException e) {
      log.error(e.getMessage(), e);
      throw new WebApplicationException(e.getMessage());
    }
  }



  /**
   * Alle Konferenzteilnehmer werden mit ihren Attributen ausgelesen.
   *
   *
   * @param konferenzname
   *                      die Konferenz
   * @param organisator
   *                      der Organisator
   *
   * @return der Protokollbefehl {@code CONFERENCE}
   * 
   */
  @GET
  @Path("/read/presence/")
  @Produces(MediaType.APPLICATION_JSON)
  public CONFERENCE readPresence(@HeaderParam(value = "konferenzname") String konferenzname,
      @HeaderParam(value = "conferencekey") String conferencekey) {

    if (konferenzname == null) {
      throw new WebApplicationException(
          Response.status(Status.BAD_REQUEST).entity("konferenzname is null").build()
      );
    }
    if (conferencekey == null) {
      throw new WebApplicationException(
          Response.status(Status.BAD_REQUEST).entity("conferencekey is null").build()
      );
    }

    try {
      String organisator = database.fetchOrganisatorByConferencekey(conferencekey);
      CONFERENCE conference = database.readKonferenzuser(konferenzname, organisator);
      conference.setConferenceKey(conferencekey);
      return conference;
    }
    catch (DatabaseException e) {
      log.fatal(e.getMessage());
      throw new WebApplicationException(e, Status.INTERNAL_SERVER_ERROR);
    }
  }



  /**
   * Lies alle Anwender ein, die den Konferenzraum betreten könnten. Der
   * Rückgabewert sind n-Paare aus (Nickname/Userid). Der Konferenzorganisator ist
   * nicht in den Paaren enthalten, weil er immer an der Konferenz teilnimmt.
   *
   * @param konferenzraum
   *                      der Konferenzraum
   *
   * @return diese Anwender könnten eingeladen werden
   * 
   */
  @Path("/lesen/users/")
  @GET
  public ArrayList<String> readRestlicheMitgliederHeader(@HeaderParam("konferenzraum") String konferenzraum,
      @HeaderParam("pseudoid") String organisator) {

    log.info("organisator {}", organisator);
    String userid = database.fetchUserByPseudoid(organisator).get();
    log.info("userid {}", userid);
    log.info("konferenzraum {}", konferenzraum);

    ArrayList<String> nicknames = database.fetchCandidateConferenceCall(konferenzraum, userid);

    return nicknames;
  }



  /**
   * Alle Konferenzräume werden ermittelt, in welchem der Anwender Mitglied ist.
   * Diese Methode wird vom Client aufgerufen.
   *
   *
   * @param userid
   *               ein Anwender
   *
   * @return eine Liste aller Konfernzräume
   */
  @GET
  @Path("/read/joined/conferences")
  @Produces(MediaType.APPLICATION_JSON)
  public List<TransferKonferenzraum> readJoinedConferences(@HeaderParam(value = "pseudoid") String pseudoid) {
    String userid = database.fetchUserByPseudoid(pseudoid).get();
    return database.meineKonferenzen(userid);
  }



  /**
   * Lies alle Telefonkonferenzen aus.
   *
   *
   * @param userid
   *               der Konferenzorganisator
   *
   * @return alle Telefonkonferenzen, die dem Anwender gehören
   */
  @Produces(MediaType.APPLICATION_JSON)
  @Path("/organize/")
  @GET
  public List<TransferOrganizeConferenceCall> readTelefonkonferenzenHeader(
      @HeaderParam("pseudoid") String pseudoid) {

    String userid = database.fetchUserByPseudoid(pseudoid).get();
    ArrayList<TransferOrganizeConferenceCall> liste = new ArrayList<>();
    List<TransferTelko> telefonkonferenzenVonUserid = readTelko(userid);
    for (TransferTelko telko : telefonkonferenzenVonUserid) {
      TransferOrganizeConferenceCall organize = new TransferOrganizeConferenceCall();
      List<String> teilnehmerNickanmes;
      try {
        teilnehmerNickanmes = database.readKonferenzteilnehmer(telko.getKonferenzraum(), userid);
      }
      catch (DatabaseException e) {
        log.error(e);
        throw new WebApplicationException(e, Status.INTERNAL_SERVER_ERROR);
      }
      organize.setZonedAblaufdatum(telko.getAblaufdatum());
      organize.setBeschreibung(telko.getBeschreibung());
      organize.setKonferenzname(telko.getKonferenzraum());
      organize.setTeilnehmer(teilnehmerNickanmes);
      liste.add(organize);
    }
    return liste;
  }



  /**
   * 
   * @param userid
   * @return
   */
  public List<TransferTelko> readTelko(String userid) {

    ArrayList<TransferTelko> listTransfer = new ArrayList<>();
    List<net.javacomm.database.entity.Telko> entity;
    try {
      entity = database.readTelefonkonferenzen(userid);
      for (net.javacomm.database.entity.Telko tmp : entity) {
        TransferTelko transfer = new TransferTelko();
        transfer.setAblaufdatum(tmp.getUntil());
        transfer.setBeschreibung(tmp.getTexto());
        transfer.setKonferenzraum(tmp.getKonferenzraum());
        transfer.setConferenceKey(tmp.getConferenceKey());
        listTransfer.add(transfer);
      }
    }
    catch (DatabaseException e) {
      log.error(e);
      throw new WebApplicationException(e, Status.INTERNAL_SERVER_ERROR);
    }

    return listTransfer;
  }



  /**
   * Der Beschreibungstext für eine Konferenz wird gespeichert.
   *
   *
   * @param text
   *             ein Beschreibungstext
   * 
   */
  @POST
  @Path("/write/description")
  @Produces(MediaType.TEXT_PLAIN)
  @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
  public boolean writeBeschreibung(@FormParam("pseudoid") String pseudoid,
      @FormParam("konferenzname") String konferenzname, @FormParam("description") String description) {

    String userid = database.fetchUserByPseudoid(pseudoid).get();
    return database.updateKonferenzbeschreibung(userid, konferenzname, description);
  }



  @POST
  @Path("/write/participants") // Keine {userid} mehr in der URL!
  @Produces(MediaType.TEXT_PLAIN)
  @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
  public Response writeTeilnehmerliste(MultivaluedMap<String, String> formParams) {

    // Bequem die Werte aus der Map ziehen
    String pseudoid = formParams.getFirst("pseudoid");
    String konferenzname = formParams.getFirst("konferenzname");
    List<String> nicknames = formParams.get("positivliste");

    if (pseudoid == null) {
      return Response.status(Status.BAD_REQUEST).entity("Parameter pseudoid is null").build();
    }
    if (konferenzname == null) {
      return Response.status(Status.BAD_REQUEST).entity("Parameter konferenzname is null").build();
    }
    // Die Positivliste sind alle Werte unter dem Key "positivliste"
    if (nicknames == null) {
      return Response.status(Status.BAD_REQUEST).entity("Parameter positivliste is null").build();
    }

    try {
      String userid = database.fetchUserByPseudoid(pseudoid).get();

      List<String> userids = nicknames.stream()
          .map(participantNickname -> database.fetchUserByNickname(participantNickname))
          .collect(Collectors.toList());

      database.writeKonferenzteilnehmer(userid, konferenzname, userids);
    }
    catch (DatabaseException e) {
      log.error(e.getMessage());
      throw new WebApplicationException(
          Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build()
      );
    }
    return Response.noContent().build();
  }



  /**
   * Das Verfallsdatum für die Telefonkonferenz wird gesetzt.
   *
   * @param userid
   *                      der Konferenzorganisator
   * @param konferenzname
   *                      der Konferenzname
   * @param expirydate
   *                      das Ablaufdatum
   *
   * @return {@code true}, das Ablaufdatum konnte gespeichert werden
   * 
   */
  @POST
  @Path("/write/expirydate/")
  @Produces(MediaType.TEXT_PLAIN)
  @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
  public boolean writeVerfallsdatum(@FormParam("pseudoid") String pseudoid,
      @FormParam("konferenzname") String konferenzname, @FormParam("expirydate") long expirydate) {

    Instant instant = Instant.ofEpochMilli(expirydate);
    ZoneId zoneId = ZoneId.systemDefault();
    LocalDateTime datetime = LocalDateTime.ofInstant(instant, zoneId);
    String userid = database.fetchUserByPseudoid(pseudoid).get();
    return database.writeVerfallsdatum(userid, konferenzname, datetime);
  }

}
