/*
 *  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.Random;
import java.util.Set;

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

    private static final Set<Character> CENTURY = Set.of( '+', '-','A' );

    private final String countryCode = "FI";
    private final String countryName = "Finland";

    @Override
    public boolean formatCheck(String tin) {
        if (tin.length() != 11) {
            return false;
        }
        var digits = tin.substring(0,6);
        if (!StringUtils.isDigits(digits)) {
            return false;
        }
        var century = tin.charAt(6);
        if (!CENTURY.contains(century)) {
            return false;
        }
        var digits2 = tin.substring(7,10);
        if (!StringUtils.isDigits(digits2)) {
            return false;
        }
        var checkChar = tin.charAt(10);
        if( !(Character.isAlphabetic(checkChar) || Character.isDigit(checkChar))) {
            return false;
        }
        return true;
    }

    @Override
    public boolean logicCheck(String tin) {
        var digits = tin.substring(0,6);
        var digits2 = tin.substring(7,10);
        var checkChar = tin.charAt(10);
        var calculatedCheckChar = getCheckChar(digits, digits2);
        return checkChar == calculatedCheckChar;
    }

    @Override
    public String generate(LocalDate yearOfBirth, Gender gender, Random randomGenerator) {

        var digits = String.format("%02d%02d%02d", yearOfBirth.getDayOfMonth(), yearOfBirth.getMonthValue(),
                yearOfBirth.getYear() % 100);

        var century = (int) Math.floor(yearOfBirth.getYear() / 100.00);
        var centuryChar = switch (century) {
            case 18 -> '+';
            case 19 -> '-';
            case 20 -> 'A';
            default -> throw new IllegalArgumentException("Invalid century");
        };

        var digits2 = String.format("%03d", randomGenerator.ints(2,899)
                .map(n -> switch (gender) {
                    case MALE -> n % 2 == 0 ? n + 1 : n;
                    case FEMALE -> n % 2 == 0 ? n : n + 1;
                })
                .findFirst()
                .getAsInt());

        var calculatedCheckChar = getCheckChar(digits, digits2);

        return digits + centuryChar + digits2 + calculatedCheckChar;
    }

    private static char getCheckChar(String digits, String digits2) {
        var calculatedCheckNumber = Integer.parseUnsignedInt(digits+digits2);
        var calculatedCheckDigit = calculatedCheckNumber % 31;

        if (calculatedCheckDigit < 10) {
            return Character.forDigit(calculatedCheckDigit, 10);
        }
        if (calculatedCheckDigit < 16) {
            return (char) ('A' + (calculatedCheckDigit-10));
        }
        if (calculatedCheckDigit == 16) {
            return 'H';
        }
        if (calculatedCheckDigit < 22) {
            return (char) ('J' + (calculatedCheckDigit-17));
        }
        if (calculatedCheckDigit == 22) {
            return 'P';
        }
        return (char) ('R' + (calculatedCheckDigit-23));
    }
}