/*
 * Copyright (c) 2010-2026 Contributors to the openHAB project
 *
 * See the NOTICE file(s) distributed with this work for additional
 * information.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 */
package org.openhab.binding.shelly.internal.util;

import java.util.Locale;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

/**
 * {@link ShellyVersionComparator} compares 2 version strings.
 *
 * @author Markus Michels - Initial contribution
 */
@NonNullByDefault
public class ShellyVersionComparator {
    private class VersionTokenizer {
        private final String versionString;
        private final int length;

        private int position;
        private int number;
        private String suffix = "";

        public VersionTokenizer(@Nullable String versionString) {
            if (versionString == null) {
                throw new IllegalArgumentException("versionString is null");
            }

            this.versionString = versionString;
            length = versionString.length();
        }

        private boolean moveNext() {
            number = 0;
            suffix = "";

            // No more characters
            if (position >= length) {
                return false;
            }

            while (position < length) {
                char c = versionString.charAt(position);
                if (c < '0' || c > '9') {
                    break;
                }
                number = number * 10 + (c - '0');
                position++;
            }

            int suffixStart = position;

            while (position < length) {
                char c = versionString.charAt(position);
                if (c == '.') {
                    break;
                }
                position++;
            }

            suffix = versionString.substring(suffixStart, position);

            if (position < length) {
                position++;
            }

            return true;
        }

        private int getNumber() {
            return number;
        }

        private String getSuffix() {
            return suffix;
        }
    }

    public boolean equals(String s1, String s2) {
        return compare(s1, s2) == 0;
    }

    public int compare(String version1, String version2) {
        VersionTokenizer tokenizer1 = new VersionTokenizer(version1);
        VersionTokenizer tokenizer2 = new VersionTokenizer(version2);

        int number1 = 0, number2 = 0;
        String suffix1 = "", suffix2 = "";

        while (tokenizer1.moveNext()) {
            if (!tokenizer2.moveNext()) {
                do {
                    number1 = tokenizer1.getNumber();
                    suffix1 = tokenizer1.getSuffix();
                    if (number1 != 0 || suffix1.length() != 0) {
                        // Version one is longer than number two, and non-zero
                        return 1;
                    }
                } while (tokenizer1.moveNext());

                // Version one is longer than version two, but zero
                return 0;
            }

            number1 = tokenizer1.getNumber();
            suffix1 = tokenizer1.getSuffix();
            number2 = tokenizer2.getNumber();
            suffix2 = tokenizer2.getSuffix();

            if (number1 < number2) {
                // Number one is less than number two
                return -1;
            }
            if (number1 > number2) {
                // Number one is greater than number two
                return 1;
            }

            boolean empty1 = suffix1.length() == 0;
            boolean empty2 = suffix2.length() == 0;

            if (empty1 && empty2) {
                continue;
            } // No suffixes
            if (empty1) {
                return 1;
            } // First suffix is empty (1.2 > 1.2b)
            if (empty2) {
                return -1;
            } // Second suffix is empty (1.2a < 1.2)

            // Lexical comparison of suffixes
            int result = suffix1.compareTo(suffix2);
            if (result != 0) {
                return result;
            }

        }

        while (tokenizer2.moveNext()) {
            number2 = tokenizer2.getNumber();
            suffix2 = tokenizer2.getSuffix();
            if (number2 != 0 || suffix2.length() != 0) {
                // Version one is longer than version two, and non-zero
                return -1;
            }
        }
        // Version two is longer than version one, but zero
        return 0;
    }

    public boolean checkBeta(@Nullable String version) {
        if (version == null) {
            return false;
        }
        String lowVersion;
        return version.isEmpty() || version.contains("???")
                || (lowVersion = version.toLowerCase(Locale.ROOT)).contains("master")
                || (lowVersion.contains("-rc") || lowVersion.contains("beta"));
    }

    /**
     * Strip trailing build hash from a firmware version string.
     *
     * Gen1 hashes start with 'g' (e.g. g9979d16); Gen2 hashes are pure lowercase hex (e.g. 06f6da23).
     * Both appear after the last '-'. Stripping them enables numeric-only version comparison.
     * Example: "2.6.2-06f6da23" → "2.6.2", "2.6.2-beta1-06f6da23" → "2.6.2-beta1".
     */
    public String stripBuildHash(String version) {
        int idx = version.lastIndexOf('-');
        if (idx > 0 && idx < version.length() - 1) {
            String tail = version.substring(idx + 1);
            // pure hex (Gen2) or g-prefixed hex (Gen1)
            String hex = tail.startsWith("g") ? tail.substring(1) : tail;
            if (!hex.isEmpty() && hex.chars().allMatch(c -> (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))) {
                return version.substring(0, idx);
            }
        }
        return version;
    }

    /**
     * Checks whether {@code candidateVersion} is a newer release than {@code installedVersion}, stripping
     * any trailing build hash from both first so e.g. "2.6.2-abc" and "2.6.2" compare as equal.
     */
    public boolean isNewer(String candidateVersion, String installedVersion) {
        return !candidateVersion.isEmpty()
                && compare(stripBuildHash(installedVersion), stripBuildHash(candidateVersion)) < 0;
    }
}
