package cz.moneta.test.dsl.util.data.generators;

import cz.moneta.test.dsl.util.data.client.Gender;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.ods.OdsEndpoint;
import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.auth.AuthSupport;
import cz.moneta.test.harness.support.data.Generator;
import org.apache.commons.lang3.tuple.Pair;
import org.jooq.Record;
import org.jooq.Result;

import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@SuppressWarnings("all")
public class RcGenerator implements Generator<String> {

    public static final LocalDate RC_DEFAULT_START_YEAR = LocalDate.of(1954, 1, 1);
    public static final Gender DEFAULT_GENDER = Gender.MAN;

    @Override
    public String generate(StoreAccessor harness, Object... params) {
        Pair<LocalDate, LocalDate> dateOfBirthRange = getDateRange(params);
        Gender gender = getGender(params);

        return getRandomRcUniqueInOds(harness, dateOfBirthRange, gender);
    }

    private Pair<LocalDate, LocalDate> getDateRange(Object[] params) {
        Optional<Object[]> pa = Optional.ofNullable(params);
        if (pa.isPresent()) {
            List<LocalDate> dates = Arrays.stream(params).filter(LocalDate.class::isInstance).limit(2).map(LocalDate.class::cast).collect(Collectors.toList());
            if (dates.size() == 2)
                return Pair.of(dates.get(0), dates.get(1));
        }

        return Pair.of(RC_DEFAULT_START_YEAR, LocalDate.now().minusYears(27));
    }

    private Gender getGender(Object[] params) {
        Optional<Object[]> p = Optional.ofNullable(params);
        if (p.isPresent()) {
            return Arrays.stream(params).filter(Gender.class::isInstance).findFirst().map(Gender.class::cast).orElse(Gender.MAN);
        }
        return DEFAULT_GENDER;
    }

    private String getRandomRcUniqueInOds(StoreAccessor harness, Pair<LocalDate, LocalDate> dateOfBirthRange, Gender gender) {
        String dbName = "ods";

        if (AuthSupport.hasCredentials(dbName, harness) == false) {
            throw new HarnessException("You using RcGenerator and need to add ODS DB credentials 'credentials." + dbName + ".username', 'credentials." + dbName + ".password' to your config");
        }

        return Stream.generate(() -> getRandomRc(dateOfBirthRange, gender))
                .limit(10)
                .peek(birthNumber -> harness.log("Generate birth number with params (from:{}, to:{}, {}): {}",
                        dateOfBirthRange.getLeft(), dateOfBirthRange.getRight(), gender.toString().toLowerCase(), birthNumber))
                .filter(birthNumber -> isBirthNumberUnique(harness, birthNumber))  //TODO temporary solution - find better way
                .findFirst()
                .orElseThrow(() -> new HarnessException("The existing RC was generated 10 times."));
    }

    private boolean isBirthNumberUnique(StoreAccessor harness, String birthNumber) {
        Result<Record> records = harness.getEndpoint(OdsEndpoint.class)
                .executeSql(getBirtNumberSql(birthNumber));
        return records.get(0)
                .getValue("RECORDCOUNT", Integer.class) == 0;
    }

    private String getBirtNumberSql(String birthNumer) {
        return "select sum(rcCount) as recordCount\n" +
                " from (select count(*) as rcCount\n" +
                " from ODS_L1_OWNER.INST_PT\n" +
                "where SRC_SYS_ID = 'ICB0'\n" +
                "  and DEL_FLAG = '0'\n" +
                "  and RC_NUM = '" + birthNumer + "'\n" +
                "union\n" +
                "select count(*) as rcCount\n" +
                "  from ODS_L1_OWNER.uni_pt\n" +
                " where DEL_FLAG = '0'\n" +
                "   and rc_num = '" + birthNumer + "')";
    }

    private String getRandomRc(Pair<LocalDate, LocalDate> dateOfBirthRange, Gender gender) {
        return getRandomRc(dateOfBirthRange.getLeft(), dateOfBirthRange.getRight(), gender);
    }

    private String getRandomRc(LocalDate defaultFrom, LocalDate defaultTo, Gender gender) {
        long from = defaultFrom.toEpochDay();
        long to = defaultTo.toEpochDay();
        LocalDate dateOfBirth = LocalDate.ofEpochDay(ThreadLocalRandom.current().nextLong(from, to + 1));
        int womanIncrease = (gender == Gender.WOMAN) ? 5000 : 0;
        long rcDatePart = 10000 * (dateOfBirth.getYear() % 100) + 100 * dateOfBirth.getMonthValue() + womanIncrease + dateOfBirth.getDayOfMonth();

        if (dateOfBirth.getYear() < 1954) {
            Long rc = Stream.generate(() -> ThreadLocalRandom.current().nextLong(1000))
                    .findFirst()
                    .map(r -> rcDatePart * 1000 + r)
                    .get();
            return String.format("%09d", rc);
        } else {
            Long rc = Stream.generate(() -> ThreadLocalRandom.current().nextLong(10000))
                    .filter(r -> (rcDatePart * 10000 + r) % 11 == 0)
                    .findFirst()
                    .map(r -> rcDatePart * 10000 + r)
                    .get();
            return String.format("%010d", rc);
        }
    }
}
