package org.mte.numecoeval.calculs.infrastructure.repository;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.mte.numecoeval.calculs.domain.data.indicateurs.*;
import org.mte.numecoeval.calculs.domain.exception.DatabaseException;
import org.mte.numecoeval.common.utils.PreparedStatementUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.List;
import java.util.Optional;

@Component
@Slf4j
public class IndicateurRepository {

    @Autowired
    private DataSource dataSource;

    private static final String INSERT_EQUIPEMENT_PHYSIQUE_QUERY = """
               INSERT INTO ind_indicateur_impact_equipement_physique
                         (date_calcul, date_lot, date_lot_discriminator, nom_organisation, nom_organisation_discriminator, etapeacv,
                           critere, nom_equipement, nom_entite, nom_entite_discriminator, source,
                           statut_indicateur, trace, version_calcul, conso_elec_moyenne,
                           impact_unitaire, quantite, type_equipement, unite,
                           statut_equipement_physique, nom_lot, nom_source_donnee, nom_source_donnee_discriminator, qualite)
                         VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?, ?, ?, ?, ?)
            """;

    private static final String INSERT_EQUIPEMENT_VIRTUEL_QUERY = """
               INSERT INTO ind_indicateur_impact_equipement_virtuel
                    (date_calcul, date_lot, date_lot_discriminator, nom_organisation, nom_organisation_discriminator,
                      etapeacv, critere, nom_equipement, nom_equipement_virtuel, nom_entite, nom_entite_discriminator, 
                      source, statut_indicateur, trace, version_calcul, impact_unitaire, unite, conso_elec_moyenne, 
                      cluster, nom_lot, nom_source_donnee, nom_source_donnee_discriminator, qualite )
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?)
            """;

    private static final String INSERT_APPLICATION_QUERY = """
            INSERT INTO ind_indicateur_impact_application
                    (date_calcul, date_lot, date_lot_discriminator, etapeacv, critere, nom_organisation,
                     nom_organisation_discriminator, nom_application, type_environnement, nom_equipement_physique,
                     nom_equipement_virtuel, nom_entite, nom_entite_discriminator, source, statut_indicateur, trace,
                     version_calcul, domaine, sous_domaine, impact_unitaire, unite, conso_elec_moyenne, nom_lot,
                     nom_source_donnee, nom_source_donnee_discriminator, qualite)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?)
            """;

    private static final String INSERT_OPERATION_NON_IT_QUERY = """
            INSERT INTO ind_indicateur_impact_operation_non_it
                (date_calcul, date_lot, date_lot_discriminator, nom_organisation, nom_organisation_discriminator, etapeacv,
                           critere, nom_item_non_it, nom_entite, nom_entite_discriminator, source,
                           statut_indicateur, trace, version_calcul, conso_elec_moyenne,
                           impact_unitaire, quantite, type_item, unite,
                           nom_lot, nom_source_donnee, nom_source_donnee_discriminator,qualite)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ,? ,?,?)
            """;
    private static final String INSERT_MESSAGERIE_QUERY = """
            INSERT INTO ind_indicateur_impact_messagerie
                (date_calcul, date_lot, date_lot_discriminator, nom_organisation, nom_organisation_discriminator,
                critere, mois_annee, nom_entite, nom_entite_discriminator, source, statut_indicateur, trace,
                version_calcul, impact_mensuel, unite, nombre_mail_emis, volume_total_mail_emis, nom_lot,
                nom_source_donnee, nom_source_donnee_discriminator)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """;

    private static final String INSERT_RESEAU_QUERY = """
                INSERT INTO ind_indicateur_impact_reseau
                        (date_calcul, date_lot, date_lot_discriminator, nom_organisation, nom_organisation_discriminator,
                        etapeacv, critere, nom_equipement, nom_entite, nom_entite_discriminator, source, statut_indicateur,
                        trace, version_calcul, impact_unitaire, unite, nom_lot, nom_source_donnee, nom_source_donnee_discriminator)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """;

    /**
     * Sauvegarde tous les indicateurs en base de données e
     *
     * @param impactsEquipementPhysique liste des impacts Eq Ph
     * @param impactsEquipementVirtuels liste des impacts Eq Virt
     * @param impactsApplications       liste des impacts applications
     */
    public void saveIndicateursEquipements(
            List<ImpactEquipementPhysique> impactsEquipementPhysique,
            List<ImpactEquipementVirtuel> impactsEquipementVirtuels,
            List<ImpactApplication> impactsApplications,
            List<ImpactReseau> impactsReseau
    ) {
        if (impactsEquipementPhysique.isEmpty()) return;

        try (Connection conn = dataSource.getConnection()) {
            conn.setAutoCommit(false);

            try (var ps = conn.prepareStatement(INSERT_EQUIPEMENT_PHYSIQUE_QUERY)) {
                for (ImpactEquipementPhysique indicateur : impactsEquipementPhysique) {
                    ps.setTimestamp(1, PreparedStatementUtils.getTimestampFromLocalDateTime(indicateur.getDateCalcul()));
                    ps.setDate(2, PreparedStatementUtils.getDateFromLocalDate(indicateur.getDateLot()));
                    ps.setDate(3, PreparedStatementUtils.getDateFromLocalDate(Optional.ofNullable(indicateur.getDateLot()).orElse(LocalDate.EPOCH)));
                    ps.setString(4, indicateur.getNomOrganisation());
                    ps.setString(5, StringUtils.defaultString(indicateur.getNomOrganisation()));
                    ps.setString(6, indicateur.getEtapeACV());
                    ps.setString(7, indicateur.getCritere());
                    ps.setString(8, indicateur.getNomEquipement());
                    ps.setString(9, indicateur.getNomEntite());
                    ps.setString(10, StringUtils.defaultString(indicateur.getNomEntite()));
                    ps.setString(11, indicateur.getSource());
                    ps.setString(12, indicateur.getStatutIndicateur());
                    ps.setString(13, indicateur.getTrace());
                    ps.setString(14, indicateur.getVersionCalcul());
                    PreparedStatementUtils.setDoubleValue(ps, 15, indicateur.getConsoElecMoyenne());
                    PreparedStatementUtils.setDoubleValue(ps, 16, indicateur.getImpactUnitaire());
                    PreparedStatementUtils.setIntValue(ps, 17, indicateur.getQuantite().intValue());
                    ps.setString(18, indicateur.getTypeEquipement());
                    ps.setString(19, indicateur.getUnite());
                    ps.setString(20, indicateur.getStatutEquipementPhysique());
                    ps.setString(21, indicateur.getNomLot());
                    ps.setString(22, indicateur.getNomSourceDonnee());
                    ps.setString(23, StringUtils.defaultString(indicateur.getNomSourceDonnee()));
                    ps.setString(24, indicateur.getQualite());
                    ps.addBatch();
                }

                ps.executeBatch();
            }
            try (var ps = conn.prepareStatement(INSERT_EQUIPEMENT_VIRTUEL_QUERY)) {
                for (ImpactEquipementVirtuel indicateur : impactsEquipementVirtuels) {
                    ps.setTimestamp(1, PreparedStatementUtils.getTimestampFromLocalDateTime(indicateur.getDateCalcul()));
                    ps.setDate(2, PreparedStatementUtils.getDateFromLocalDate(indicateur.getDateLot()));
                    ps.setDate(3, PreparedStatementUtils.getDateFromLocalDate(Optional.ofNullable(indicateur.getDateLot()).orElse(LocalDate.EPOCH)));
                    ps.setString(4, indicateur.getNomOrganisation());
                    ps.setString(5, StringUtils.defaultString(indicateur.getNomOrganisation()));
                    ps.setString(6, indicateur.getEtapeACV());
                    ps.setString(7, indicateur.getCritere());
                    ps.setString(8, indicateur.getNomEquipement());
                    ps.setString(9, indicateur.getNomEquipementVirtuel());
                    ps.setString(10, indicateur.getNomEntite());
                    ps.setString(11, StringUtils.defaultString(indicateur.getNomEntite()));
                    ps.setString(12, indicateur.getSource());
                    ps.setString(13, indicateur.getStatutIndicateur());
                    ps.setString(14, indicateur.getTrace());
                    ps.setString(15, indicateur.getVersionCalcul());
                    PreparedStatementUtils.setDoubleValue(ps, 16, indicateur.getImpactUnitaire());
                    ps.setString(17, indicateur.getUnite());
                    PreparedStatementUtils.setDoubleValue(ps, 18, indicateur.getConsoElecMoyenne());
                    ps.setString(19, indicateur.getCluster());
                    ps.setString(20, indicateur.getNomLot());
                    ps.setString(21, indicateur.getNomSourceDonnee());
                    ps.setString(22, StringUtils.defaultString(indicateur.getNomSourceDonnee()));
                    ps.setString(23, indicateur.getQualite());
                    ps.addBatch();
                }

                ps.executeBatch();
            }

            try (var ps = conn.prepareStatement(INSERT_APPLICATION_QUERY)) {
                for (ImpactApplication indicateur : impactsApplications) {
                    ps.setTimestamp(1, PreparedStatementUtils.getTimestampFromLocalDateTime(indicateur.getDateCalcul()));
                    ps.setDate(2, PreparedStatementUtils.getDateFromLocalDate(indicateur.getDateLot()));
                    ps.setDate(3, PreparedStatementUtils.getDateFromLocalDate(Optional.ofNullable(indicateur.getDateLot()).orElse(LocalDate.EPOCH)));
                    ps.setString(4, indicateur.getEtapeACV());
                    ps.setString(5, indicateur.getCritere());
                    ps.setString(6, indicateur.getNomOrganisation());
                    ps.setString(7, StringUtils.defaultString(indicateur.getNomOrganisation()));
                    ps.setString(8, indicateur.getNomApplication());
                    ps.setString(9, indicateur.getTypeEnvironnement());
                    ps.setString(10, indicateur.getNomEquipementPhysique());
                    ps.setString(11, indicateur.getNomEquipementVirtuel());
                    ps.setString(12, indicateur.getNomEntite());
                    ps.setString(13, StringUtils.defaultString(indicateur.getNomEntite()));
                    ps.setString(14, indicateur.getSource());
                    ps.setString(15, indicateur.getStatutIndicateur());
                    ps.setString(16, indicateur.getTrace());
                    ps.setString(17, indicateur.getVersionCalcul());
                    ps.setString(18, indicateur.getDomaine());
                    ps.setString(19, indicateur.getSousDomaine());
                    PreparedStatementUtils.setDoubleValue(ps, 20, indicateur.getImpactUnitaire());
                    ps.setString(21, indicateur.getUnite());
                    PreparedStatementUtils.setDoubleValue(ps, 22, indicateur.getConsoElecMoyenne());
                    ps.setString(23, indicateur.getNomLot());
                    ps.setString(24, indicateur.getNomSourceDonnee());
                    ps.setString(25, StringUtils.defaultString(indicateur.getNomSourceDonnee()));
                    ps.setString(26, indicateur.getQualite());
                    ps.addBatch();
                }

                ps.executeBatch();
            }

            try (var ps = conn.prepareStatement(INSERT_RESEAU_QUERY)) {

                for (ImpactReseau indicateur : impactsReseau) {
                    ps.setTimestamp(1, PreparedStatementUtils.getTimestampFromLocalDateTime(indicateur.getDateCalcul()));
                    ps.setDate(2, PreparedStatementUtils.getDateFromLocalDate(indicateur.getDateLot()));
                    ps.setDate(3, PreparedStatementUtils.getDateFromLocalDate(Optional.ofNullable(indicateur.getDateLot()).orElse(LocalDate.EPOCH)));
                    ps.setString(4, indicateur.getNomOrganisation());
                    ps.setString(5, StringUtils.defaultString(indicateur.getNomOrganisation()));
                    ps.setString(6, indicateur.getEtapeACV());
                    ps.setString(7, indicateur.getCritere());
                    ps.setString(8, indicateur.getNomEquipement());
                    ps.setString(9, indicateur.getNomEntite());
                    ps.setString(10, StringUtils.defaultString(indicateur.getNomEntite()));
                    ps.setString(11, indicateur.getSource());
                    ps.setString(12, indicateur.getStatutIndicateur());
                    ps.setString(13, indicateur.getTrace());
                    ps.setString(14, indicateur.getVersionCalcul());
                    PreparedStatementUtils.setDoubleValue(ps, 15, indicateur.getImpactUnitaire());
                    ps.setString(16, indicateur.getUnite());
                    ps.setString(17, indicateur.getNomLot());
                    ps.setString(18, indicateur.getNomSourceDonnee());
                    ps.setString(19, StringUtils.defaultString(indicateur.getNomSourceDonnee()));
                    ps.addBatch();
                }

                ps.executeBatch();
            }

            conn.commit();
        } catch (SQLException e) {
            log.error("Une erreur s'est produite lors de l'insertion dans PostgreSQL. Exception: ", e);
            throw new DatabaseException(e);
        }
    }

    /**
     * Sauvegarde tous les indicateurs 'OperationNonIT' en base de données
     *
     * @param impactOperationNonITs liste indicateurs des Opérations non IT
     */
    public void saveIndicateursOperationNonIT(List<ImpactOperationNonIT> impactOperationNonITs) {
        if (impactOperationNonITs.isEmpty()) return;

        try (Connection conn = dataSource.getConnection()) {
            conn.setAutoCommit(false);

            try (var ps = conn.prepareStatement(INSERT_OPERATION_NON_IT_QUERY)) {

                for (ImpactOperationNonIT indicateur : impactOperationNonITs) {
                    ps.setTimestamp(1, PreparedStatementUtils.getTimestampFromLocalDateTime(indicateur.getDateCalcul()));
                    ps.setDate(2, PreparedStatementUtils.getDateFromLocalDate(indicateur.getDateLot()));
                    ps.setDate(3, PreparedStatementUtils.getDateFromLocalDate(Optional.ofNullable(indicateur.getDateLot()).orElse(LocalDate.EPOCH)));
                    ps.setString(4, indicateur.getNomOrganisation());
                    ps.setString(5, StringUtils.defaultString(indicateur.getNomOrganisation()));
                    ps.setString(6, indicateur.getEtapeACV());
                    ps.setString(7, indicateur.getCritere());
                    ps.setString(8, indicateur.getNomItemNonIT());
                    ps.setString(9, indicateur.getNomEntite());
                    ps.setString(10, StringUtils.defaultString(indicateur.getNomEntite()));
                    ps.setString(11, indicateur.getSource());
                    ps.setString(12, indicateur.getStatutIndicateur());
                    ps.setString(13, indicateur.getTrace());
                    ps.setString(14, indicateur.getVersionCalcul());
                    PreparedStatementUtils.setDoubleValue(ps, 15, indicateur.getConsoElecMoyenne());
                    PreparedStatementUtils.setDoubleValue(ps, 16, indicateur.getImpactUnitaire());
                    PreparedStatementUtils.setIntValue(ps, 17, indicateur.getQuantite().intValue());
                    ps.setString(18, indicateur.getTypeItem());
                    ps.setString(19, indicateur.getUnite());
                    ps.setString(20, indicateur.getNomLot());
                    ps.setString(21, indicateur.getNomSourceDonnee());
                    ps.setString(22, StringUtils.defaultString(indicateur.getNomSourceDonnee()));
                    ps.setString(23, indicateur.getQualite());
                    ps.addBatch();
                }

                ps.executeBatch();
            }

            conn.commit();
        } catch (SQLException e) {
            log.error("Une erreur s'est produite lors de l'insertion dans PostgreSQL. Exception: ", e);
            throw new DatabaseException(e);
        }
    }

    /**
     * Sauvegarde tous les indicateurs 'messagerie' en base de données
     *
     * @param impactMessageries liste indicateurs messagerie
     */
    public void saveIndicateursMessagerie(List<ImpactMessagerie> impactMessageries) {
        if (impactMessageries.isEmpty()) return;

        try (Connection conn = dataSource.getConnection()) {
            conn.setAutoCommit(false);

            try (var ps = conn.prepareStatement(INSERT_MESSAGERIE_QUERY)) {

                for (ImpactMessagerie indicateur : impactMessageries) {
                    ps.setTimestamp(1, PreparedStatementUtils.getTimestampFromLocalDateTime(indicateur.getDateCalcul()));
                    ps.setDate(2, PreparedStatementUtils.getDateFromLocalDate(indicateur.getDateLot()));
                    ps.setDate(3, PreparedStatementUtils.getDateFromLocalDate(Optional.ofNullable(indicateur.getDateLot()).orElse(LocalDate.EPOCH)));
                    ps.setString(4, indicateur.getNomOrganisation());
                    ps.setString(5, StringUtils.defaultString(indicateur.getNomOrganisation()));
                    ps.setString(6, indicateur.getCritere());
                    ps.setDate(7, PreparedStatementUtils.getDateFromLocalDate(moisAnneeToLocalDate(indicateur.getMoisAnnee())));
                    ps.setString(8, indicateur.getNomEntite());
                    ps.setString(9, StringUtils.defaultString(indicateur.getNomEntite()));
                    ps.setString(10, indicateur.getSource());
                    ps.setString(11, indicateur.getStatutIndicateur());
                    ps.setString(12, indicateur.getTrace());
                    ps.setString(13, indicateur.getVersionCalcul());
                    PreparedStatementUtils.setDoubleValue(ps, 14, indicateur.getImpactMensuel());
                    ps.setString(15, indicateur.getUnite());
                    PreparedStatementUtils.setDoubleValue(ps, 16, indicateur.getNombreMailEmis());
                    PreparedStatementUtils.setDoubleValue(ps, 17, indicateur.getVolumeTotalMailEmis());
                    ps.setString(18, indicateur.getNomLot());
                    ps.setString(19, indicateur.getNomSourceDonnee());
                    ps.setString(20, StringUtils.defaultString(indicateur.getNomSourceDonnee()));
                    ps.addBatch();
                }

                ps.executeBatch();
            }

            conn.commit();
        } catch (SQLException e) {
            log.error("Une erreur s'est produite lors de l'insertion dans PostgreSQL. Exception: ", e);
            throw new DatabaseException(e);
        }
    }

    private LocalDate moisAnneeToLocalDate(Integer moisAnnee) {
        if (moisAnnee == null) return null;
        try {
            return LocalDate.parse(moisAnnee + "01", DateTimeFormatter.ofPattern("yyyyMMdd"));
        } catch (DateTimeParseException ignored) {
            return null;
        }
    }

    /**
     * Supprime les indicateurs des equipements physiques, virtuel et applications
     *
     * @param nomEquipementPhysique le nom de l'equipement physique
     * @param nomLot                le nomLot
     */
    public void clearIndicateursEquipement(String nomEquipementPhysique, String nomLot) {

        try (Connection conn = dataSource.getConnection()) {
            conn.setAutoCommit(false);

            try (var ps = conn.prepareStatement("""
                    DELETE FROM ind_indicateur_impact_equipement_physique
                    WHERE nom_lot = ? AND nom_equipement = ?
                    """)) {
                ps.setString(1, nomLot);
                ps.setString(2, nomEquipementPhysique);
                ps.execute();
            }

            try (var ps = conn.prepareStatement("""
                    DELETE FROM ind_indicateur_impact_equipement_virtuel
                    WHERE nom_lot = ? AND nom_equipement = ?
                    """)) {
                ps.setString(1, nomLot);
                ps.setString(2, nomEquipementPhysique);
                ps.execute();
            }

            try (var ps = conn.prepareStatement("""
                    DELETE FROM ind_indicateur_impact_application
                    WHERE nom_lot = ? AND nom_equipement_physique = ?
                    """)) {
                ps.setString(1, nomLot);
                ps.setString(2, nomEquipementPhysique);
                ps.execute();
            }

            try (var ps = conn.prepareStatement("""
                    DELETE FROM ind_indicateur_impact_reseau
                    WHERE nom_lot = ? AND nom_equipement = ?
                    """)) {
                ps.setString(1, nomLot);
                ps.setString(2, nomEquipementPhysique);
                ps.execute();
            }
            conn.commit();
        } catch (SQLException e) {
            log.error("Une erreur s'est produite lors de l'insertion dans PostgreSQL. Exception: ", e);
            throw new DatabaseException(e);
        }
    }

    public void clearIndicateursOperation(String nomOperationNonIT, String nomLot) {

        try (Connection conn = dataSource.getConnection()) {
            conn.setAutoCommit(false);

            try (var ps = conn.prepareStatement("""
                    DELETE FROM ind_indicateur_impact_operation_non_it
                    WHERE nom_lot = ? AND nom_item_non_it = ?
                    """)) {
                ps.setString(1, nomLot);
                ps.setString(2, nomOperationNonIT);
                ps.execute();
            }
            conn.commit();
        } catch (SQLException e) {
            log.error("Une erreur s'est produite lors de l'insertion dans PostgreSQL. Exception: ", e);
            throw new DatabaseException(e);
        }
    }
}
