/*
 * Copyright 2025 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package xyz.zhouxy.plusone.commons.model;


import static xyz.zhouxy.plusone.commons.util.AssertTools.checkArgument;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Nullable;

import com.google.common.base.Splitter;

import xyz.zhouxy.plusone.commons.util.StringTools;

// TODO [优化] 优化正则表达式
// TODO [补充] 完善单元测试
// TODO [doc] javadoc、README.md

/**
 * SemVer 语义版本号
 *
 * @author ZhouXY108 <luquanlion@outlook.com>
 * @since 1.1.0
 *
 * @see <a href="https://semver.org/">Semantic Versioning 2.0.0</a>
 */
public class SemVer implements Comparable<SemVer>, Serializable {
    private static final long serialVersionUID = 458265121025514002L;

    private final String value;

    private final int[] versionNumbers;
    private final String[] preReleaseVersion;
    private final String buildMetadata;

    private static final String VERSION_NUMBERS = "(?<numbers>(?<major>0|[1-9]\\d*)\\.(?<minor>0|[1-9]\\d*)\\.(?<patch>0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){0,2})";
    private static final String PRE_RELEASE_VERSION = "(?:-(?<prerelease>(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?";
    private static final String BUILD_METADATA = "(?:\\+(?<buildmetadata>[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?";

    private static final Pattern PATTERN = Pattern.compile(
            "^" + VERSION_NUMBERS + PRE_RELEASE_VERSION + BUILD_METADATA + "$");

    private SemVer(String value, int[] versionNumbers, @Nullable String[] preReleaseVersion, @Nullable String buildMetadata) {
        this.value = value;
        this.versionNumbers = versionNumbers;
        this.preReleaseVersion = preReleaseVersion;
        this.buildMetadata = buildMetadata;
    }

    public static SemVer of(final String value) {
        checkArgument(StringTools.isNotBlank(value), "版本号不能为空");
        final Matcher matcher = PATTERN.matcher(value);
        checkArgument(matcher.matches(), "版本号格式错误");
        // 数字版本部分
        final String versionNumbersPart = matcher.group("numbers");
        // 先行版本号部分
        final String preReleaseVersionPart = matcher.group("prerelease");
        // 版本编译信息部分
        final String buildMetadataPart = matcher.group("buildmetadata");

        final int[] versionNumbers = Splitter.on('.')
                .splitToStream(versionNumbersPart)
                // 必须都是数字
                .mapToInt(Integer::parseInt)
                .toArray();

        final String[] preReleaseVersion = preReleaseVersionPart != null
                ? Splitter.on('.').splitToStream(preReleaseVersionPart).toArray(String[]::new)
                : null;

        return new SemVer(value, versionNumbers, preReleaseVersion, buildMetadataPart);
    }

    public int getMajor() {
        return this.versionNumbers[0];
    }

    public int getMinor() {
        return this.versionNumbers[1];
    }

    public int getPatch() {
        return this.versionNumbers[2];
    }

    public String getBuildMetadata() {
        return buildMetadata;
    }

    @Override
    public int compareTo(@Nullable SemVer that) {
        if (that == null) {
            return 1;
        }
        int result = compareVersionNumbers(that);
        if (result != 0) {
            return result;
        }
        return comparePreReleaseVersion(that);
    }

    public String getValue() {
        return value;
    }



    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + Arrays.hashCode(versionNumbers);
        result = prime * result + Arrays.hashCode(preReleaseVersion);
        result = prime * result + Objects.hash(value, buildMetadata);
        return result;
    }

    @Override
    public boolean equals(@Nullable Object obj) {
        if (this == obj)
            return true;
        if (!(obj instanceof SemVer))
            return false;
        SemVer other = (SemVer) obj;
        return Objects.equals(value, other.value) && Arrays.equals(versionNumbers, other.versionNumbers)
                && Arrays.equals(preReleaseVersion, other.preReleaseVersion)
                && Objects.equals(buildMetadata, other.buildMetadata);
    }

    @Override
    public String toString() {
        return 'v' + value;
    }

    private int compareVersionNumbers(SemVer that) {
        final int minLength = Integer.min(this.versionNumbers.length, that.versionNumbers.length);

        for (int i = 0; i < minLength; i++) {
            final int currentVersionNumberOfThis = this.versionNumbers[i];
            final int currentVersionNumberOfThat = that.versionNumbers[i];
            if (currentVersionNumberOfThis != currentVersionNumberOfThat) {
                return currentVersionNumberOfThis - currentVersionNumberOfThat;
            }
        }
        return this.versionNumbers.length - that.versionNumbers.length;
    }

    private int comparePreReleaseVersion(SemVer that) {
        final String[] preReleaseVersionOfThis = this.preReleaseVersion;
        final String[] preReleaseVersionOfThat = that.preReleaseVersion;
        byte thisWithoutPreReleaseVersionFlag = preReleaseVersionOfThis == null ? (byte) 1 : (byte) 0;
        byte thatWithoutPreReleaseVersionFlag = preReleaseVersionOfThat == null ? (byte) 1 : (byte) 0;
        if ((thisWithoutPreReleaseVersionFlag | thatWithoutPreReleaseVersionFlag) == 1) {
            return thisWithoutPreReleaseVersionFlag - thatWithoutPreReleaseVersionFlag;
        }
        @SuppressWarnings("null")
        final int minLength = Integer.min(preReleaseVersionOfThis.length, preReleaseVersionOfThat.length);
        for (int i = 0; i < minLength; i++) {
            int r = comparePartOfPreReleaseVersion(preReleaseVersionOfThis[i], preReleaseVersionOfThat[i]);
            if (r != 0) {
                return r;
            }
        }
        return preReleaseVersionOfThis.length - preReleaseVersionOfThat.length;
    }

    private static int comparePartOfPreReleaseVersion(String p1, String p2) {
        boolean p1IsNumber = isAllDigits(p1);
        boolean p2IsNumber = isAllDigits(p2);

        if (p1IsNumber) {
            return p2IsNumber
                    ? Integer.parseInt(p1) - Integer.parseInt(p2) // 都是数字
                    : -1; // p1 是数字，p2 是字符串
        }
        // 如果 p1 是字符串，p2 是数字，则返回 1（字符串优先于纯数字）
        return p2IsNumber ? 1 : p1.compareTo(p2);
    }

    private static boolean isAllDigits(@Nullable String str) {
        if (str == null || str.isEmpty()) {
            return false;
        }

        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (c < '0' || c > '9') {
                return false;
            }
        }
        return true;
    }
}
