/*
 *  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 jakarta.inject.Singleton;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import org.apache.commons.lang3.StringUtils;

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

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

    private final String countryCode = "IT";
    private final String countryName = "Italy";

    private static final List< Character> MONTHS = List.of(
        'A','B','C','D','E','H','L','M','P','R','S','T'
    );

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

    private static final Map<Character, Integer> EVEN_POSITION_MAP = new HashMap<>();
    static {
        EVEN_POSITION_MAP.put('A', 0);
        EVEN_POSITION_MAP.put('B', 1);
        EVEN_POSITION_MAP.put('C', 2);
        EVEN_POSITION_MAP.put('D', 3);
        EVEN_POSITION_MAP.put('E', 4);
        EVEN_POSITION_MAP.put('F', 5);
        EVEN_POSITION_MAP.put('G', 6);
        EVEN_POSITION_MAP.put('H', 7);
        EVEN_POSITION_MAP.put('I', 8);
        EVEN_POSITION_MAP.put('J', 9);
        EVEN_POSITION_MAP.put('K', 10);
        EVEN_POSITION_MAP.put('L', 11);
        EVEN_POSITION_MAP.put('M', 12);
        EVEN_POSITION_MAP.put('N', 13);
        EVEN_POSITION_MAP.put('O', 14);
        EVEN_POSITION_MAP.put('P', 15);
        EVEN_POSITION_MAP.put('Q', 16);
        EVEN_POSITION_MAP.put('R', 17);
        EVEN_POSITION_MAP.put('S', 18);
        EVEN_POSITION_MAP.put('T', 19);
        EVEN_POSITION_MAP.put('U', 20);
        EVEN_POSITION_MAP.put('V', 21);
        EVEN_POSITION_MAP.put('W', 22);
        EVEN_POSITION_MAP.put('X', 23);
        EVEN_POSITION_MAP.put('Y', 24);
        EVEN_POSITION_MAP.put('Z', 25);
        EVEN_POSITION_MAP.put('0', 0);
        EVEN_POSITION_MAP.put('1', 1);
        EVEN_POSITION_MAP.put('2', 2);
        EVEN_POSITION_MAP.put('3', 3);
        EVEN_POSITION_MAP.put('4', 4);
        EVEN_POSITION_MAP.put('5', 5);
        EVEN_POSITION_MAP.put('6', 6);
        EVEN_POSITION_MAP.put('7', 7);
        EVEN_POSITION_MAP.put('8', 8);
        EVEN_POSITION_MAP.put('9', 9);
    }

    private static final Map<Character, Integer> ODD_POSITION_MAP = new HashMap<>();
    static {
        ODD_POSITION_MAP.put('A', 1);
        ODD_POSITION_MAP.put('B', 0);
        ODD_POSITION_MAP.put('C', 5);
        ODD_POSITION_MAP.put('D', 7);
        ODD_POSITION_MAP.put('E', 9);
        ODD_POSITION_MAP.put('F', 13);
        ODD_POSITION_MAP.put('G', 15);
        ODD_POSITION_MAP.put('H', 17);
        ODD_POSITION_MAP.put('I', 19);
        ODD_POSITION_MAP.put('J', 21);
        ODD_POSITION_MAP.put('K', 2);
        ODD_POSITION_MAP.put('L', 4);
        ODD_POSITION_MAP.put('M', 18);
        ODD_POSITION_MAP.put('N', 20);
        ODD_POSITION_MAP.put('O', 11);
        ODD_POSITION_MAP.put('P', 3);
        ODD_POSITION_MAP.put('Q', 6);
        ODD_POSITION_MAP.put('R', 8);
        ODD_POSITION_MAP.put('S', 12);
        ODD_POSITION_MAP.put('T', 14);
        ODD_POSITION_MAP.put('U', 16);
        ODD_POSITION_MAP.put('V', 10);
        ODD_POSITION_MAP.put('W', 22);
        ODD_POSITION_MAP.put('X', 25);
        ODD_POSITION_MAP.put('Y', 24);
        ODD_POSITION_MAP.put('Z', 23);
        ODD_POSITION_MAP.put('0', 1);
        ODD_POSITION_MAP.put('1', 0);
        ODD_POSITION_MAP.put('2', 5);
        ODD_POSITION_MAP.put('3', 7);
        ODD_POSITION_MAP.put('4', 9);
        ODD_POSITION_MAP.put('5', 13);
        ODD_POSITION_MAP.put('6', 15);
        ODD_POSITION_MAP.put('7', 17);
        ODD_POSITION_MAP.put('8', 19);
        ODD_POSITION_MAP.put('9', 21);
    }

    @Override
    public String cleanTin(String tin) {
        return TinCountry.super.cleanTin(tin).toUpperCase();
    }

    @Override
    public String generate(LocalDate yearOfBirth, Gender gender, Random randomGenerator) {
        var codeList =
                EVEN_POSITION_MAP.keySet().stream().map(Object::toString).toList();
        var code = randomGenerator
                .ints(6, 0, codeList.size())
                .mapToObj(codeList::get)
                .collect(Collectors.joining(""));
        var year = StringUtils.leftPad(String.valueOf(yearOfBirth.getYear() % 100),2,'0');
        var month = MONTHS.get(yearOfBirth.getMonthValue() - 1).toString();
        var day = StringUtils.leftPad(String.valueOf(gender == Gender.MALE ?
                yearOfBirth.getDayOfMonth() : yearOfBirth.getDayOfMonth() + 40),2,'0');
        var pob = String.format("%s%d%d%d",CHECK_DIGIT.get(randomGenerator.nextInt(0, CHECK_DIGIT.size())),
                + randomGenerator.nextInt(0,10),
                + randomGenerator.nextInt(0,10),
                + randomGenerator.nextInt(0,10));
        var tin = code + year + month + day + pob;
        return tin + calculateCheckDigit(tin);
    }

    @Override
    public boolean formatCheck(String tin) {
        if (tin.length() != 16) {
            return false;
        }

        var digits = List.of(6,7,9,10,12,13,14);
        for (Integer digit : digits) {
            if (!Character.isDigit(tin.charAt(digit))) {
                return false;
            }
        }

        if (!MONTHS.contains(tin.charAt(8))) {
            return false;
        }

        var age = Integer.parseInt(tin.substring(9,11));
        var isMale = age >= 1 && age <=31;
        var isFemale = age >= 41 && age <= 71;
        if (!isMale && !isFemale) {
            return false;
        }
        return true;
    }

    @Override
    public boolean logicCheck(String tin) {
        return calculateCheckDigit(tin) == tin.charAt(15);
    }

    private static char calculateCheckDigit(String tin) {
        var sum = 0;
        for (int i = 0; i < 15; i++) {
            var c = tin.charAt(i);
            var n = (i+1) % 2 == 0 ? EVEN_POSITION_MAP.get(c) : ODD_POSITION_MAP.get(c);
            sum += n;
        }
        sum = sum % CHECK_DIGIT.size();
        return CHECK_DIGIT.get(sum);
    }
}