/*
 *  Copyright (c) 2025 John Ahlroos / Devsoap
 *
 *  Creative Commons Attribution-NoDerivatives 4.0 International Public License
 *
 *  By exercising the Licensed Rights (defined below), You accept and agree
 *  to be bound by the terms and conditions of this Creative Commons
 *  Attribution-NoDerivatives 4.0 International Public License ("Public
 *  License"). To the extent this Public License may be interpreted as a
 *  contract, You are granted the Licensed Rights in consideration of Your
 *  acceptance of these terms and conditions, and the Licensor grants You
 *  such rights in consideration of benefits the Licensor receives from
 *  making the Licensed Material available under these terms and
 *  conditions.
 *
 *  Refer to LICENSE file or https://creativecommons.org/licenses/by-nd/4.0 for full license.
 *
 */

package com.devsoap.tincheck.countries;

import com.devsoap.tincheck.tin.TinCountry;
import io.micronaut.core.util.StringUtils;
import jakarta.inject.Singleton;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;

import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;

import static org.apache.commons.lang3.StringUtils.*;

@Getter
@Singleton
@RequiredArgsConstructor
@ToString(of = {"countryCode", "countryName"})
public class Country_ES implements TinCountry {

    private static final List<Character> SPAIN_1_LETTERS = List.of(
            'T', 'R', 'W', 'A', 'G', 'M', 'Y', 'F', 'P', 'D', 'X',
            'B', 'N', 'J', 'Z', 'S', 'Q', 'V', 'H', 'L', 'C', 'K', 'E'
    );

    private static final Map<Character, Character> SPAIN_2_LETTERS = Map.of(
            'X','0',
            'Y','1',
            'Z','2',
            'K','0',
            'L','0',
            'M','0'
    );

    private final String countryCode = "ES";
    private final String countryName = "Spain";

    @Override
    public String cleanTin(String tin) {
        return leftPad(tin, 9, '0');
    }

    @Override
    public boolean formatCheck(String tin) {
        if (tin.length() != 9) {
            return false;
        }
        return isSpain1(tin) || isSpain2(tin);
    }

    @Override
    public boolean logicCheck(String tin) {
        if (isSpain2(tin)) {
            tin = SPAIN_2_LETTERS.get(tin.charAt(0)) + tin.substring(1);
        }
        var sum = Integer.parseUnsignedInt(tin.substring(0,8)) % SPAIN_1_LETTERS.size();
        return tin.charAt(8) == SPAIN_1_LETTERS.get(sum);
    }

    @Override
    public String generate(LocalDate yearOfBirth, Gender gender, Random randomGenerator) {
        var version1 = randomGenerator.nextBoolean();
        if (version1) {
            var zeroes = randomGenerator.nextInt(0,5);
            var numbers = randomGenerator
                    .ints(1,9)
                    .limit(8 - zeroes)
                    .mapToObj(String::valueOf)
                    .collect(Collectors.joining());
            var sum = Integer.parseUnsignedInt(numbers) % SPAIN_1_LETTERS.size();
            return numbers + SPAIN_1_LETTERS.get(sum);
        } else {
            var leadingChar = (Character) SPAIN_2_LETTERS.keySet().toArray()[(randomGenerator.nextInt(0, SPAIN_2_LETTERS.size()))];
            var numbers = randomGenerator
                    .ints(1,9)
                    .limit(7)
                    .mapToObj(String::valueOf)
                    .collect(Collectors.joining());

            var sum = Integer.parseUnsignedInt(SPAIN_2_LETTERS.get(leadingChar) + numbers) % SPAIN_1_LETTERS.size();
            return leadingChar + numbers + SPAIN_1_LETTERS.get(sum);
        }
    }

    private boolean isSpain1(String tin) {
        return StringUtils.isDigits(tin.substring(0,8)) && SPAIN_1_LETTERS.contains(tin.charAt(8));
    }

    private boolean isSpain2(String tin) {
        return StringUtils.isDigits(tin.substring(1,8))
                && SPAIN_2_LETTERS.containsKey(tin.charAt(0))
                && SPAIN_1_LETTERS.contains(tin.charAt(8));
    }
}