/*
 * Axelor Business Solutions
 *
 * Copyright (C) 2019 Axelor (<http://axelor.com>).
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.axelor.apps.hr.service.extra.hours;

import com.axelor.apps.base.db.Company;
import com.axelor.apps.base.db.repo.CompanyRepository;
import com.axelor.apps.base.service.app.AppBaseService;
import com.axelor.apps.hr.db.DailyReport;
import com.axelor.apps.hr.db.Employee;
import com.axelor.apps.hr.db.ExtraHours;
import com.axelor.apps.hr.db.ExtraHoursLine;
import com.axelor.apps.hr.db.HRConfig;
import com.axelor.apps.hr.db.repo.DailyReportRepository;
import com.axelor.apps.hr.db.repo.EmployeeRepository;
import com.axelor.apps.hr.db.repo.ExtraHoursRepository;
import com.axelor.apps.hr.service.config.HRConfigService;
import com.axelor.apps.message.db.Message;
import com.axelor.apps.message.service.TemplateMessageService;
import com.axelor.auth.AuthUtils;
import com.axelor.exception.AxelorException;
import com.google.inject.Inject;
import com.google.inject.persist.Transactional;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.List;
import javax.mail.MessagingException;
import wslite.json.JSONException;
import wslite.json.JSONObject;

public class ExtraHoursServiceImpl implements ExtraHoursService {

  protected ExtraHoursRepository extraHoursRepo;
  protected AppBaseService appBaseService;
  protected HRConfigService hrConfigService;
  protected TemplateMessageService templateMessageService;
  protected CompanyRepository companyRepo;
  protected DailyReportRepository dailyReportRepo;
  protected EmployeeRepository employeeRepo;

  @Inject
  public ExtraHoursServiceImpl(
      EmployeeRepository employeeRepo,
      CompanyRepository companyRepo,
      DailyReportRepository dailyReportRepo,
      ExtraHoursRepository extraHoursRepo,
      AppBaseService appBaseService,
      HRConfigService hrConfigService,
      TemplateMessageService templateMessageService) {

    this.extraHoursRepo = extraHoursRepo;
    this.employeeRepo = employeeRepo;
    this.companyRepo = companyRepo;
    this.dailyReportRepo = dailyReportRepo;
    this.appBaseService = appBaseService;
    this.hrConfigService = hrConfigService;
    this.templateMessageService = templateMessageService;
  }

  @Transactional(rollbackOn = {Exception.class})
  public void cancel(ExtraHours extraHours) throws AxelorException {

    extraHours.setStatusSelect(ExtraHoursRepository.STATUS_CANCELED);
    extraHoursRepo.save(extraHours);
  }

  public Message sendCancellationEmail(ExtraHours extraHours)
      throws AxelorException, ClassNotFoundException, InstantiationException,
          IllegalAccessException, MessagingException, IOException {

    HRConfig hrConfig = hrConfigService.getHRConfig(extraHours.getCompany());

    if (hrConfig.getTimesheetMailNotification()) {

      return templateMessageService.generateAndSendMessage(
          extraHours, hrConfigService.getCanceledExtraHoursTemplate(hrConfig));
    }

    return null;
  }

  @Transactional(rollbackOn = {Exception.class})
  public void confirm(ExtraHours extraHours) throws AxelorException {

    extraHours.setStatusSelect(ExtraHoursRepository.STATUS_CONFIRMED);
    extraHours.setSentDate(appBaseService.getTodayDate());

    extraHoursRepo.save(extraHours);
  }

  public Message sendConfirmationEmail(ExtraHours extraHours)
      throws AxelorException, ClassNotFoundException, InstantiationException,
          IllegalAccessException, MessagingException, IOException {

    HRConfig hrConfig = hrConfigService.getHRConfig(extraHours.getCompany());

    if (hrConfig.getExtraHoursMailNotification()) {

      return templateMessageService.generateAndSendMessage(
          extraHours, hrConfigService.getSentExtraHoursTemplate(hrConfig));
    }

    return null;
  }

  @Transactional(rollbackOn = {Exception.class})
  public void validate(ExtraHours extraHours) throws AxelorException {

    extraHours.setStatusSelect(ExtraHoursRepository.STATUS_VALIDATED);
    extraHours.setValidatedBy(AuthUtils.getUser());
    extraHours.setValidationDate(appBaseService.getTodayDate());

    extraHoursRepo.save(extraHours);
  }

  public Message sendValidationEmail(ExtraHours extraHours)
      throws AxelorException, ClassNotFoundException, InstantiationException,
          IllegalAccessException, MessagingException, IOException {

    HRConfig hrConfig = hrConfigService.getHRConfig(extraHours.getCompany());

    if (hrConfig.getExtraHoursMailNotification()) {

      return templateMessageService.generateAndSendMessage(
          extraHours, hrConfigService.getValidatedExtraHoursTemplate(hrConfig));
    }

    return null;
  }

  @Transactional(rollbackOn = {Exception.class})
  public void refuse(ExtraHours extraHours) throws AxelorException {

    extraHours.setStatusSelect(ExtraHoursRepository.STATUS_REFUSED);
    extraHours.setRefusedBy(AuthUtils.getUser());
    extraHours.setRefusalDate(appBaseService.getTodayDate());

    extraHoursRepo.save(extraHours);
  }

  public Message sendRefusalEmail(ExtraHours extraHours)
      throws AxelorException, ClassNotFoundException, InstantiationException,
          IllegalAccessException, MessagingException, IOException {

    HRConfig hrConfig = hrConfigService.getHRConfig(extraHours.getCompany());

    if (hrConfig.getExtraHoursMailNotification()) {

      return templateMessageService.generateAndSendMessage(
          extraHours, hrConfigService.getRefusedExtraHoursTemplate(hrConfig));
    }

    return null;
  }

  @Override
  public void compute(ExtraHours extraHours) {

    BigDecimal totalQty = BigDecimal.ZERO;
    List<ExtraHoursLine> extraHoursLines = extraHours.getExtraHoursLineList();

    for (ExtraHoursLine extraHoursLine : extraHoursLines) {
      totalQty = totalQty.add(extraHoursLine.getQty());
    }

    extraHours.setTotalQty(totalQty);
  }

  @Transactional(rollbackOn = {Exception.class})
  public void saveExtraHours(JSONObject jsonObject) throws AxelorException {
    Company company = companyRepo.all().filter("self.id = 1").fetchOne();
    try {
      // Extract fields
      int idInt = jsonObject.getInt("id");
      String id = Integer.toString(idInt);
      String ticketLink = "https://dsi.sophal.dz/front/ticket.form.php?id=" + id;
      String matricule = jsonObject.getString("matricule");
      String dateRequisition = jsonObject.getString("date_requisition");
      String motifRequisition = jsonObject.getString("motif_requisition");
      String heureDebut = jsonObject.getString("heure_debut");
      String heureFin = jsonObject.getString("heure_fin");
      String lieuTravail = jsonObject.getString("lieu_travail");
      int validation_status = jsonObject.optInt("validation_status", 2);
      String validateByUser = jsonObject.optString("validate_by_user", null);
      String dateValidation = jsonObject.optString("validation_date", null);

      // GET EMPLOYEES
      Employee employee =
          employeeRepo
              .all()
              .filter("self.registrationNumber = :matricule")
              .bind("matricule", matricule)
              .fetchOne();

      if (employee == null) {
        System.err.println("Employee with matricule " + matricule + " not found.");
        return;
      }

      Employee validatedByEmployee = null;

      if (validateByUser != null) {
        validatedByEmployee =
            employeeRepo
                .all()
                .filter("self.registrationNumber = :matricule")
                .bind("matricule", validateByUser)
                .fetchOne();

        if (validatedByEmployee == null) {
          System.err.println("Validator employee with matricule " + validateByUser + " not found.");
          return;
        }
      }

      // Parse validation date (handle null case)
      LocalDate validationDate = null;
      if (dateValidation != null && !dateValidation.isEmpty()) {
        try {
          OffsetDateTime offsetDateTime =
              OffsetDateTime.parse(dateValidation, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
          validationDate = offsetDateTime.toLocalDate(); // Extract only the date part
        } catch (DateTimeParseException e) {
          System.out.println("Error parsing dateValidation: " + dateValidation);
          validationDate = null;
        }
      }

      // Parse Requisition Date
      LocalDate requisitionDate;
      try {
        requisitionDate = LocalDate.parse(dateRequisition);
      } catch (DateTimeParseException e) {
        System.out.println("Error parsing dateRequisition: " + dateRequisition);
        requisitionDate = null;
      }

      // Check if Authorization exists by ticketId
      ExtraHours extraHours =
          extraHoursRepo
              .all()
              .filter("self.ticketId = :ticketId")
              .bind("ticketId", idInt)
              .fetchOne();

      if (extraHours != null) {
        // Authorization exists, compare previous and new status
        int previousStatus = extraHours.getStatusSelect(); // Previous status
        int newStatus = validation_status; // New status

        if (previousStatus == 2 && newStatus == 3) {
          System.out.println(
              "Tickets :" + idInt + " Status changed from " + previousStatus + " to " + newStatus);
          // Update the fields of the existing Authorization
          extraHours.setValidatedByEmployee(validatedByEmployee);
          extraHours.setValidationDate(validationDate);
          extraHours.setStatusSelect(newStatus);
          // Save the updated Authorization
          extraHoursRepo.save(extraHours);

          // Get Daily report
          DailyReport dailyReport =
              dailyReportRepo
                  .all()
                  .filter("self.employee = :employee and self.reportDate = :reportDate")
                  .bind("employee", employee)
                  .bind("reportDate", requisitionDate)
                  .fetchOne();

          if (dailyReport != null) {
            dailyReport.setIsValidSupHours(true);
          }
        } else if (previousStatus == 2 && newStatus == 4) {
          System.out.println(
              "Tickets :" + idInt + " Status changed from " + previousStatus + " to " + newStatus);
          extraHours.setRefusedByEmployee(validatedByEmployee);
          extraHours.setRefusalDate(validationDate);
          extraHours.setStatusSelect(newStatus);
          // Save the updated Authorization
          extraHoursRepo.save(extraHours);
        }
      } else {
        // Create an instance of ExtraHours
        extraHours = new ExtraHours();
        extraHours.setEmployee(employee);
        extraHours.setTicket(ticketLink);
        extraHours.setTicketId(idInt);
        extraHours.setRequisitionDate(requisitionDate);
        extraHours.setDescription(motifRequisition);
        extraHours.setStatusSelect(validation_status);
        extraHours.setCompany(company);

        // Parse Start and End Hours
        try {
          extraHours.setStartHour(LocalTime.parse(heureDebut));
        } catch (DateTimeParseException e) {
          // Append the problematic heureDebut to the description
          String updatedDescription = extraHours.getDescription() + " - " + heureDebut;
          extraHours.setDescription(updatedDescription);
          extraHours.setStartHour(LocalTime.MIDNIGHT); // Optionally set a default value
        }

        try {
          extraHours.setEndHour(LocalTime.parse(heureFin));
        } catch (DateTimeParseException e) {
          // Append the problematic heureFin to the description
          String updatedDescription = extraHours.getDescription() + " - " + heureFin;
          extraHours.setDescription(updatedDescription);
          extraHours.setEndHour(LocalTime.MIDNIGHT); // Optionally set a default value
        }

        if (validation_status == 3) {
          extraHours.setValidatedByEmployee(validatedByEmployee);
          extraHours.setValidationDate(validationDate);
        } else if (validation_status == 4) {
          extraHours.setRefusedByEmployee(validatedByEmployee);
          extraHours.setRefusalDate(validationDate);
        }

        // Save the ExtraHours entity
        extraHoursRepo.save(extraHours);

        // Get Daily report
        DailyReport dailyReport =
            dailyReportRepo
                .all()
                .filter("self.employee = :employee and self.reportDate = :reportDate")
                .bind("employee", employee)
                .bind("reportDate", requisitionDate)
                .fetchOne();

        if (dailyReport != null) {
          // Get the existing list of ExtraHours
          List<ExtraHours> supHoursList = dailyReport.getSupHoursList();

          // If the list is null, initialize it
          if (supHoursList == null) {
            supHoursList = new ArrayList<>();
          }

          // Add the new ExtraHours to the list
          supHoursList.add(extraHours);
          if (validation_status == 3) dailyReport.setIsValidSupHours(true);
          // Set the updated list back to dailyReport
          dailyReport.setSupHoursList(supHoursList);
        }
      }
    } catch (JSONException e) {
      System.err.println("Failed to parse JSON: " + jsonObject.toString());
      e.printStackTrace();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}
