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

import cz.moneta.test.dsl.util.task.udebs.UdebsTasks;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.data.Generator;
import org.apache.commons.lang3.StringUtils;

import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Stream;

/**
 * Generator of bank account number
 *
 * Usage:
 * if you want random account number, just call harness.generate(GeneratorType.ACCOUNT_NUMBER);
 * if you want unique partner account number for inkaso, you must specific client account number in parameter
 * - for example harness.generate(GeneratorType.ACCOUNT_NUMBER, "215620573");
 *
 * Sources:
 * https://testy/generatory.html
 * https://k3a.me/mod11.php
 * https://www.radyzezivota.cz/jak-na-vlastni-cislo-bankovniho-uctu/
 */
public class AccountNumberGenerator implements Generator<String> {

    public String generate(StoreAccessor harness, Object... params) {
        if (params.length == 0) {
            return getRandomAccNum();
        }

        if (!(params[0] instanceof String)) {
            throw new HarnessException("Unrecognized parameter");
        }

        String clientAccNum = (String) params[0];

        return Stream.generate(this::getRandomAccNum)
                .limit(10)
                .filter(partnerAccNum -> !new UdebsTasks(harness).isExistInkasoCombination(clientAccNum, partnerAccNum))
                .findFirst()
                .orElseThrow(() -> new HarnessException("Generated existing combination 10 times"));
    }

    private String getRandomAccNum() {
        return Stream.generate(() -> ThreadLocalRandom.current().nextInt(1, 2147483647))
                .filter(r -> checkMod11(String.valueOf(r)))
                .findFirst()
                .get()
                .toString();
    }

    private boolean checkMod11(String base) {
        int length = base.length();
        if (length > 10) {
            return false;
        } else if (length < 10) {
            // pad with zeros
            base = StringUtils.leftPad(base, 10, '0');
        }

        int[] weights = new int[]{1, 2, 4, 8, 5, 10, 9, 7, 3, 6};

        int sum = 0;
        String reversed = StringUtils.reverse(base);
        for (int i = 0; i < reversed.length(); i++) {
            sum += Integer.valueOf(reversed.substring(i, i + 1)) * weights[i];
        }

        return sum % 11 == 0;
    }
}
