package ru.nbch.credit_tracker.service.indic;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
public class FlagsService2 {

    private static final String SELECT_RU_PASDUEARREAR_DATA =
            "SELECT FID, ACC_SERIAL_NUM, REPORTED_DT, AMT_PAST_DUE, DAYS_PAST_DUE " +
                    "FROM RU_PASTDUEARREAR " +
                    "WHERE FID IN (:fids) " +
                    "ORDER BY FID, REPORTED_DT DESC " +
                    "WITH UR";

    private static final String SELECT_RU_PAYMT_DATA =
            "SELECT FID, DAYS_PAST_DUE, REPORTED_DT " +
                    "FROM RU_PAYMT " +
                    "WHERE FID IN (:fids) " +
                    "AND REPORTED_DT >= :reportedDate " +
                    "WITH UR";

    private final NamedParameterJdbcTemplate jdbcTemplate;

    public FlagsService2(@Qualifier("jdbcTemplateIndic") NamedParameterJdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public Map<Integer, FlagsService.FlagsData> findFlags(List<Integer> fids) {
        if (fids.isEmpty()) {
            return new HashMap<>();
        }
        List<Integer> distinctFids = fids.stream()
                .distinct()
                .sorted(Comparator.naturalOrder())
                .toList();

        log.debug("Fids count: {}", distinctFids.size());
        Map<Integer, Integer> flags5000 = new HashMap<>();
        Map<Integer, Integer> flags90DaysByPastDueArrear = new HashMap<>();
        Map<Integer, Integer> flags90DaysByRuPaymt = new HashMap<>();
        findFlagsRuPastDueArrear(distinctFids, flags5000, flags90DaysByPastDueArrear);
        findFlagsRuPaymt(distinctFids, flags90DaysByRuPaymt);

        Map<Integer, FlagsService.FlagsData> result = new HashMap<>();
        fids.forEach(fid -> {
            Integer flagDebt5000 = flags5000.get(fid);
            if (flagDebt5000 == null) {
                flagDebt5000 = 0;
            }

            Integer flag90days = flags90DaysByPastDueArrear.get(fid);
            if (flag90days == null || flag90days == 0) {
                flag90days = flags90DaysByRuPaymt.get(fid);
                if (flag90days == null) {
                    flag90days = 0;
                }
            }
            result.put(fid, new FlagsService.FlagsData(flagDebt5000, flag90days));
        });

        return result;
    }

    /**
     * Выбирает данные из RU_PASTDUEARREAR и ищет по ним флаги просрочек
     * По FID из  RU_PASTDUEARREAR выбираются все уникальные ACC_SERIAL_NUM с максимальной датой REPORTED_DT.
     * Если по хотя бы одной такой записи AMT_PAST_DUE > 5000, то flag500 устанавливается в 1.
     * Если ни по одному счёту условие не выполняется или счетов вообще не найдено, флаг = 0.
     * <p>
     * flag90Days = 1, если выполняется хотя бы одно из условий:
     * В INDIC.RU_PASTDUEARREAR у данного FID есть запись с DAYS_PAST_DUE ≥ 90 и REPORTED_DT >= текущая дата – 12 месяцев.
     * Если ни по одной записи условия не выполняются или записей вообще не найдено, флаг = 0.
     *
     * @param fids        - отсортирован по возрастанию фидов
     * @param flag5000Map - рещультирующая мапа флагов просрочек debt5000
     * @param flag90Days  - рещультирующая мапа флагов просрочек 90_days
     */
    private void findFlagsRuPastDueArrear(List<Integer> fids, Map<Integer, Integer> flag5000Map, Map<Integer, Integer> flag90Days) {
        log.debug("Start calculating flag 5000 and 90 days by RU_PASTDUEARREAR");
        MapSqlParameterSource params = new MapSqlParameterSource();
        params.addValue("fids", fids);

        jdbcTemplate.query(SELECT_RU_PASDUEARREAR_DATA, params, (ResultSet rs) -> {
            Integer currentFid = null;
            BigInteger prevAccSerialNum = null;
            boolean processed5000ForCurrentId = false;
            boolean processed90DaysForCurrentId = false;
            LocalDate yearAgo = LocalDate.now().minusYears(1);

            while (rs.next()) {
                Integer fid = rs.getInt("FID");

                // Переход к новому fid. Сброс состояния
                if (!fid.equals(currentFid)) {
                    currentFid = fid;
                    prevAccSerialNum = null;
                    processed5000ForCurrentId = false;
                    processed90DaysForCurrentId = false;

                    // Инициализируем значения по умолчанию
                    flag5000Map.putIfAbsent(fid, 0);
                    flag90Days.putIfAbsent(fid, 0);
                }

                // Если для текущего FID уже обработали оба флага, пропускаем остальные записи
                if (processed5000ForCurrentId && processed90DaysForCurrentId) {
                    continue;
                }

                // Обработка флага 5000
                if (!processed5000ForCurrentId && flag5000Map.get(fid) == 0) {
                    BigInteger currentAccSerialNum = getBigInteger(rs, "ACC_SERIAL_NUM");
                    BigDecimal amtPastDue = getBigDecimal(rs, "AMT_PAST_DUE");

                    if (prevAccSerialNum == null || !prevAccSerialNum.equals(currentAccSerialNum)) {
                        if (amtPastDue != null && amtPastDue.compareTo(BigDecimal.valueOf(5000)) >= 0) {
                            flag5000Map.put(fid, 1);
                            processed5000ForCurrentId = true;
                        }
                        prevAccSerialNum = currentAccSerialNum;
                    }
                }

                // Обработка флага 90 дней
                if (!processed90DaysForCurrentId) {
                    Integer currentFlag90 = flag90Days.get(fid);

                    if (currentFlag90 != null && currentFlag90 == 1) {
                        processed90DaysForCurrentId = true;
                        continue;
                    }

                    if (currentFlag90 == null || currentFlag90 == 0) {
                        BigInteger days = getBigInteger(rs, "DAYS_PAST_DUE");
                        LocalDate reportedDate = rs.getObject("REPORTED_DT", LocalDate.class);

                        if (days != null) {
                            boolean has90daysFlag = days.compareTo(BigInteger.valueOf(90)) >= 0
                                    && reportedDate != null && !reportedDate.isBefore(yearAgo);

                            if (has90daysFlag) {
                                flag90Days.put(fid, 1);
                                processed90DaysForCurrentId = true;
                            } else {
                                flag90Days.put(fid, 0);
                            }
                        } else {
                            flag90Days.put(fid, 0);
                        }
                    }
                }
            }
            return null;
        });
        log.debug("Complete calculating flag 5000 and 90 days by RU_PASTDUEARREAR");
    }

    /**
     * Выбирает данные из RU_PAYMT и ищет по ним флаги просрочек 90Days
     * <p>
     * flag90Days = 1, если выполняется хотя бы одно из условий:
     * В INDIC.RU_PAYMT у данного FID есть запись с DAYS_PAST_DUE ≥ 90 и REPORTED_DT >= текущая дата – 12 месяцев.
     * Если ни по одной записи условия не выполняются или записей вообще не найдено, флаг = 0.
     *
     * @param fids       - отсортирован по возрастанию фидов
     * @param flag90Days -  рещультирующая мапа флагов просрочек 90_days
     */
    private void findFlagsRuPaymt(List<Integer> fids, Map<Integer, Integer> flag90Days) {
        log.debug("Start calculating flag 90 days by RU_PAPAYMT");
        LocalDate yearAgo = LocalDate.now().minusYears(1);
        MapSqlParameterSource params = new MapSqlParameterSource();
        params.addValue("fids", fids);
        params.addValue("reportedDate", yearAgo);
        jdbcTemplate.query(SELECT_RU_PAYMT_DATA, params, rs -> {
            Integer currentFid = null;
            boolean processed90DaysForCurrentId = false;

            while (rs.next()) {
                Integer fid = rs.getInt("FID");

                // Переход к новому fid. Сброс состояния
                if (!fid.equals(currentFid)) {
                    currentFid = fid;
                    processed90DaysForCurrentId = false;

                    // Инициализируем значения по умолчанию
                    flag90Days.putIfAbsent(fid, 0);
                }

                // Обработка флага 90 дней
                if (!processed90DaysForCurrentId) {
                    Integer currentFlag90 = flag90Days.get(fid);

                    if (currentFlag90 != null && currentFlag90 == 1) {
                        processed90DaysForCurrentId = true;
                        continue;
                    }

                    if (currentFlag90 == null || currentFlag90 == 0) {
                        BigInteger days = getBigInteger(rs, "DAYS_PAST_DUE");
                        LocalDate date = rs.getObject("REPORTED_DT", LocalDate.class);

                        if (days != null) {
                            boolean has90daysFlag = days.compareTo(BigInteger.valueOf(90)) >= 0
                                    && date != null && !date.isBefore(yearAgo);

                            if (has90daysFlag) {
                                flag90Days.put(fid, 1);
                                processed90DaysForCurrentId = true;
                            } else {
                                flag90Days.put(fid, 0);
                            }
                        } else {
                            flag90Days.put(fid, 0);
                        }
                    }
                }
            }
            return null;
        });
        log.debug("Complete calculating flag 90 days by RU_PAPAYMT");
    }


    private BigInteger getBigInteger(ResultSet rs, String columnName) {
        try {
            String value = rs.getString(columnName);
            return value != null ? new BigInteger(value) : null;
        } catch (SQLException e) {
            return null;
        }
    }

    private BigDecimal getBigDecimal(ResultSet rs, String columnName) {
        try {
            return rs.getBigDecimal(columnName);
        } catch (SQLException e) {
            return null;
        }
    }
}
