package at.procon.eventhub.reference;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class TachographNationRegistry {

    private static final Pattern UNKNOWN_NUMERIC_PATTERN = Pattern.compile("(?i)^unknown\\s+([0-9]+)$");
    private static final String RESOURCE_PATH = "/reference/nation.csv";
    private static final Map<Integer, NationRecord> BY_NUMERIC_CODE;
    private static final Map<String, NationRecord> BY_ALPHA_CODE;

    static {
        Map<Integer, NationRecord> byNumeric = new LinkedHashMap<>();
        Map<String, NationRecord> byAlpha = new LinkedHashMap<>();
        try {
            var inputStream = TachographNationRegistry.class.getResourceAsStream(RESOURCE_PATH);
            if (inputStream == null) {
                throw new IllegalStateException("Missing tachograph nation reference data " + RESOURCE_PATH);
            }
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
                reader.readLine();
                String line;
                while ((line = reader.readLine()) != null) {
                    if (line.isBlank()) {
                        continue;
                    }
                    String[] parts = line.split(";", -1);
                    if (parts.length < 4) {
                        continue;
                    }
                    Integer numericCode = parseInteger(parts[3]);
                    if (numericCode == null) {
                        continue;
                    }
                    String name = normalizeNullable(parts[1]);
                    String alphaCode = normalizeAlpha(parts[2]);
                    String defaultLanguageCode = parts.length > 4 ? normalizeNullable(parts[4]) : null;
                    NationRecord record = new NationRecord(numericCode, alphaCode, name, defaultLanguageCode, true);
                    byNumeric.put(numericCode, record);
                    if (alphaCode != null) {
                        byAlpha.put(alphaCode, record);
                    }
                }
            }
        } catch (IOException ex) {
            throw new IllegalStateException("Failed to load tachograph nation reference data.", ex);
        }
        BY_NUMERIC_CODE = Collections.unmodifiableMap(byNumeric);
        BY_ALPHA_CODE = Collections.unmodifiableMap(byAlpha);
    }

    private TachographNationRegistry() {
    }

    public static NationResolution resolve(String legacyNation, Integer nationNumericCode) {
        String normalizedLegacyNation = normalizeNullable(legacyNation);
        if (nationNumericCode != null) {
            NationRecord record = BY_NUMERIC_CODE.get(nationNumericCode);
            if (record != null) {
                return new NationResolution(
                        record.alphaCode(),
                        record.numericCode(),
                        record.name(),
                        true
                );
            }
            return new NationResolution(
                    normalizedLegacyNation != null ? normalizedLegacyNation : unknownLabel(nationNumericCode),
                    nationNumericCode,
                    unknownLabel(nationNumericCode),
                    false
            );
        }
        if (normalizedLegacyNation == null) {
            return NationResolution.empty();
        }
        Integer parsedNumericCode = parseInteger(normalizedLegacyNation);
        if (parsedNumericCode != null) {
            return resolve(null, parsedNumericCode);
        }
        Matcher unknownMatcher = UNKNOWN_NUMERIC_PATTERN.matcher(normalizedLegacyNation);
        if (unknownMatcher.matches()) {
            Integer unknownNumericCode = parseInteger(unknownMatcher.group(1));
            if (unknownNumericCode != null) {
                return resolve(normalizedLegacyNation, unknownNumericCode);
            }
        }
        NationRecord record = BY_ALPHA_CODE.get(normalizedLegacyNation.toUpperCase(Locale.ROOT));
        if (record != null) {
            return new NationResolution(
                    record.alphaCode(),
                    record.numericCode(),
                    record.name(),
                    true
            );
        }
        return new NationResolution(normalizedLegacyNation, null, null, false);
    }

    public static Integer numericCode(String legacyNation, Integer nationNumericCode) {
        return resolve(legacyNation, nationNumericCode).numericCode();
    }

    public static String alphaCode(String legacyNation, Integer nationNumericCode) {
        return resolve(legacyNation, nationNumericCode).legacyNation();
    }

    public static NationRecord recordByNumericCode(Integer numericCode) {
        if (numericCode == null) {
            return null;
        }
        NationRecord record = BY_NUMERIC_CODE.get(numericCode);
        return record != null
                ? record
                : new NationRecord(numericCode, null, unknownLabel(numericCode), null, false);
    }

    private static String normalizeNullable(String value) {
        if (value == null) {
            return null;
        }
        String trimmed = value.trim();
        if (trimmed.equalsIgnoreCase("NULL")) {
            return null;
        }
        return trimmed.isEmpty() ? null : trimmed;
    }

    private static String normalizeAlpha(String value) {
        String normalized = normalizeNullable(value);
        return normalized == null ? null : normalized.toUpperCase(Locale.ROOT);
    }

    private static Integer parseInteger(String value) {
        String normalized = normalizeNullable(value);
        if (normalized == null) {
            return null;
        }
        try {
            return Integer.parseInt(normalized);
        } catch (NumberFormatException ignored) {
            return null;
        }
    }

    private static String unknownLabel(Integer numericCode) {
        return numericCode == null ? null : "Unknown " + numericCode;
    }

    public record NationRecord(
            Integer numericCode,
            String alphaCode,
            String name,
            String defaultLanguageCode,
            boolean known
    ) {
    }

    public record NationResolution(
            String legacyNation,
            Integer numericCode,
            String displayName,
            boolean known
    ) {
        public static NationResolution empty() {
            return new NationResolution(null, null, null, false);
        }
    }
}
