package xyz.kusoft.argusupdater

internal object ArgusSemanticVersion {
    fun compare(left: String?, right: String?): Int {
        val leftParsed = SemanticVersion.parse(left)
        val rightParsed = SemanticVersion.parse(right)

        return when {
            leftParsed != null && rightParsed != null -> leftParsed.compareTo(rightParsed)
            leftParsed != null -> 1
            rightParsed != null -> -1
            else -> left.orEmpty().compareTo(right.orEmpty(), ignoreCase = true)
        }
    }

    private data class SemanticVersion(
        val major: Int,
        val minor: Int,
        val patch: Int,
        val preReleaseIdentifiers: List<Identifier>
    ) : Comparable<SemanticVersion> {
        val isPreRelease: Boolean
            get() = preReleaseIdentifiers.isNotEmpty()

        override fun compareTo(other: SemanticVersion): Int {
            major.compareTo(other.major).takeIf { it != 0 }?.let { return it }
            minor.compareTo(other.minor).takeIf { it != 0 }?.let { return it }
            patch.compareTo(other.patch).takeIf { it != 0 }?.let { return it }

            if (!isPreRelease && !other.isPreRelease) {
                return 0
            }
            if (!isPreRelease) {
                return 1
            }
            if (!other.isPreRelease) {
                return -1
            }

            val count = maxOf(preReleaseIdentifiers.size, other.preReleaseIdentifiers.size)
            for (index in 0 until count) {
                if (index >= preReleaseIdentifiers.size) {
                    return -1
                }
                if (index >= other.preReleaseIdentifiers.size) {
                    return 1
                }

                preReleaseIdentifiers[index].compareTo(other.preReleaseIdentifiers[index])
                    .takeIf { it != 0 }
                    ?.let { return it }
            }

            return 0
        }

        companion object {
            fun parse(value: String?): SemanticVersion? {
                if (value.isNullOrBlank()) {
                    return null
                }

                var trimmed = value.trim()
                if (trimmed.length > 1 &&
                    (trimmed[0] == 'v' || trimmed[0] == 'V') &&
                    trimmed[1].isDigit()
                ) {
                    trimmed = trimmed.substring(1)
                }

                val withoutBuildMetadata = trimmed.substringBefore('+')
                val corePart = withoutBuildMetadata.substringBefore('-')
                val preReleasePart = withoutBuildMetadata.substringAfter('-', missingDelimiterValue = "")
                    .takeIf { it.isNotBlank() }

                val coreSegments = corePart
                    .split('.')
                    .map(String::trim)
                    .filter(String::isNotEmpty)
                if (coreSegments.size !in 1..3) {
                    return null
                }

                val major = coreSegments.getOrNull(0)?.parseNonNegativeInt() ?: return null
                val minor = coreSegments.getOrNull(1)?.parseNonNegativeInt() ?: 0
                val patch = coreSegments.getOrNull(2)?.parseNonNegativeInt() ?: 0

                val preReleaseIdentifiers = preReleasePart
                    ?.split('.')
                    ?.map(String::trim)
                    ?.filter(String::isNotEmpty)
                    ?.map { Identifier.parse(it) ?: return null }
                    .orEmpty()

                return SemanticVersion(major, minor, patch, preReleaseIdentifiers)
            }

            private fun String.parseNonNegativeInt(): Int? =
                toIntOrNull()?.takeIf { it >= 0 }
        }
    }

    private data class Identifier(
        val isNumeric: Boolean,
        val numericValue: Long,
        val textValue: String
    ) : Comparable<Identifier> {
        override fun compareTo(other: Identifier): Int =
            when {
                isNumeric && other.isNumeric -> numericValue.compareTo(other.numericValue)
                isNumeric -> -1
                other.isNumeric -> 1
                else -> textValue.compareTo(other.textValue, ignoreCase = true)
            }

        companion object {
            fun parse(value: String): Identifier? {
                val trimmed = value.trim()
                if (trimmed.isBlank()) {
                    return null
                }
                if (!trimmed.all { it.isLetterOrDigit() || it == '-' }) {
                    return null
                }

                val numericValue = trimmed.toLongOrNull()
                return if (numericValue != null) {
                    Identifier(isNumeric = true, numericValue = numericValue, textValue = trimmed)
                } else {
                    Identifier(isNumeric = false, numericValue = 0L, textValue = trimmed)
                }
            }
        }
    }
}

internal object ArgusUpdateVersionPolicy {
    fun compareReleaseToInstalled(release: ArgusRelease, installedApp: ArgusInstalledApp): Int? {
        val installedVersionName = installedApp.versionName
            ?.trim()
            ?.takeIf { it.isNotBlank() }
            ?: return null

        return ArgusSemanticVersion.compare(release.resolvedVersionName, installedVersionName)
    }
}
