package com.seven.ikar.kotlin.core.update

data class ArgusAppUpdateConfig(
    val baseUrl: String,
    val slug: String,
    val platform: String = "android",
    val channel: String = "stable"
)

data class InstalledIkarApp(
    val packageName: String,
    val versionCode: Long,
    val versionName: String?
)

data class ArgusPublishedRelease(
    val id: String,
    val version: String,
    val channel: String,
    val platform: String,
    val packageKind: String,
    val androidVersionCode: Long?,
    val downloadPath: String,
    val originalFileName: String,
    val contentType: String,
    val packageSizeBytes: Long,
    val sha256: String,
    val publishedAt: String,
    val notes: String?
) {
    val resolvedVersionName: String
        get() = version.substringBefore('+').trim().ifBlank { version.trim() }

    val resolvedVersionCode: Long?
        get() = androidVersionCode?.takeIf { it > 0 }
            ?: version.substringAfterLast('+', missingDelimiterValue = "")
                .trim()
                .toLongOrNull()
                ?.takeIf { it > 0 }
            ?: parseVersionCodeFromFileName(originalFileName)

    companion object {
        private val VersionCodeFileNameRegex = Regex(
            """(?:^|[-_])(\d+)\.apk$""",
            RegexOption.IGNORE_CASE
        )

        fun parseVersionCodeFromFileName(fileName: String?): Long? {
            if (fileName.isNullOrBlank()) {
                return null
            }

            return VersionCodeFileNameRegex
                .find(fileName.trim())
                ?.groupValues
                ?.getOrNull(1)
                ?.toLongOrNull()
                ?.takeIf { it > 0 }
        }
    }
}

data class ArgusAppManifest(
    val slug: String,
    val name: String,
    val summary: String,
    val description: String,
    val androidPackageName: String?,
    val repositoryUrl: String?,
    val homepageUrl: String?,
    val release: ArgusPublishedRelease
)

data class AvailableIkarUpdate(
    val config: ArgusAppUpdateConfig,
    val installedApp: InstalledIkarApp,
    val manifest: ArgusAppManifest
) {
    val versionCode: Long
        get() = manifest.release.resolvedVersionCode ?: 0L

    val downloadUrl: String
        get() {
            val rawPath = manifest.release.downloadPath.trim()
            if (rawPath.startsWith("http://", ignoreCase = true) ||
                rawPath.startsWith("https://", ignoreCase = true)
            ) {
                return rawPath
            }

            return config.baseUrl.trimEnd('/') + "/" + rawPath.trimStart('/')
        }
}

internal object ArgusReleaseVersionComparator {
    fun compare(remoteVersion: String, localVersion: String?): Int {
        val remote = normalize(remoteVersion)
        val local = normalize(localVersion.orEmpty())

        if (remote.isBlank()) {
            return 0
        }
        if (local.isBlank()) {
            return 1
        }

        val remoteSemantic = SemanticVersion.parse(remote)
        val localSemantic = SemanticVersion.parse(local)
        return if (remoteSemantic != null && localSemantic != null) {
            remoteSemantic.compareTo(localSemantic)
        } else {
            remote.compareTo(local, ignoreCase = true)
        }
    }

    private fun normalize(version: String): String =
        version.trim()
            .removePrefix("v")
            .removePrefix("V")
            .substringBefore('+')
            .trim()

    private data class SemanticVersion(
        val major: Long,
        val minor: Long,
        val patch: Long,
        val preRelease: List<String>
    ) : Comparable<SemanticVersion> {
        override fun compareTo(other: SemanticVersion): Int =
            compareValuesBy(this, other, SemanticVersion::major, SemanticVersion::minor, SemanticVersion::patch)
                .takeIf { it != 0 }
                ?: comparePreRelease(preRelease, other.preRelease)

        companion object {
            private val Pattern = Regex("""^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([0-9A-Za-z.-]+))?$""")

            fun parse(value: String): SemanticVersion? {
                val match = Pattern.matchEntire(value) ?: return null
                return SemanticVersion(
                    major = match.groupValues[1].toLongOrNull() ?: return null,
                    minor = match.groupValues.getOrNull(2)?.takeIf { it.isNotBlank() }?.toLongOrNull() ?: 0L,
                    patch = match.groupValues.getOrNull(3)?.takeIf { it.isNotBlank() }?.toLongOrNull() ?: 0L,
                    preRelease = match.groupValues.getOrNull(4)
                        ?.takeIf { it.isNotBlank() }
                        ?.split('.')
                        .orEmpty()
                )
            }

            private fun comparePreRelease(left: List<String>, right: List<String>): Int {
                if (left.isEmpty() && right.isEmpty()) {
                    return 0
                }
                if (left.isEmpty()) {
                    return 1
                }
                if (right.isEmpty()) {
                    return -1
                }

                val maxSize = maxOf(left.size, right.size)
                for (index in 0 until maxSize) {
                    val leftPart = left.getOrNull(index) ?: return -1
                    val rightPart = right.getOrNull(index) ?: return 1
                    val comparison = comparePreReleasePart(leftPart, rightPart)
                    if (comparison != 0) {
                        return comparison
                    }
                }
                return 0
            }

            private fun comparePreReleasePart(left: String, right: String): Int {
                val leftNumber = left.toLongOrNull()
                val rightNumber = right.toLongOrNull()
                return when {
                    leftNumber != null && rightNumber != null -> leftNumber.compareTo(rightNumber)
                    leftNumber != null -> -1
                    rightNumber != null -> 1
                    else -> left.compareTo(right, ignoreCase = true)
                }
            }
        }
    }
}

sealed interface AppUpdateCheckResult {
    data class NotPublished(val reason: String) : AppUpdateCheckResult

    data class UpToDate(val manifest: ArgusAppManifest?) : AppUpdateCheckResult

    data class UpdateAvailable(val update: AvailableIkarUpdate) : AppUpdateCheckResult

    data class Incompatible(val reason: String) : AppUpdateCheckResult

    data class Failed(val reason: String, val cause: Throwable? = null) : AppUpdateCheckResult
}

sealed interface AppUpdateSubmissionResult {
    data class Submitted(val message: String) : AppUpdateSubmissionResult

    data class Rejected(val message: String) : AppUpdateSubmissionResult
}

data class AppUpdateInstallerEvent(
    val state: String,
    val packageName: String?,
    val releaseVersion: String?,
    val versionCode: Long?,
    val detail: String?,
    val updatedAtEpochMillis: Long
)

object AppUpdateInstallerEventState {
    const val Submitted = "submitted"
    const val ApprovalRequired = "approval_required"
    const val Installed = "installed"
    const val Failed = "failed"
}
