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

import org.json.JSONObject
import java.io.File
import java.net.HttpURLConnection
import java.net.URL
import java.security.MessageDigest

class ArgusAppUpdateClient {
    fun fetchManifest(config: ArgusAppUpdateConfig): ArgusAppManifest? {
        val url = buildManifestUrl(config)
        val connection = openConnection(url)

        return connection.useJsonBody { statusCode, body ->
            when (statusCode) {
                HttpURLConnection.HTTP_OK -> parseManifest(body)
                HttpURLConnection.HTTP_NOT_FOUND -> null
                else -> throw IllegalStateException("Argus returned HTTP $statusCode for $url")
            }
        }
    }

    fun downloadRelease(update: AvailableIkarUpdate, targetFile: File): File {
        targetFile.parentFile?.mkdirs()
        if (targetFile.exists() && sha256(targetFile).equals(update.manifest.release.sha256, ignoreCase = true)) {
            return targetFile
        }

        val connection = openConnection(update.downloadUrl)
        connection.requestMethod = "GET"
        connection.instanceFollowRedirects = true

        try {
            val statusCode = connection.responseCode
            if (statusCode != HttpURLConnection.HTTP_OK) {
                throw IllegalStateException("Argus download failed with HTTP $statusCode")
            }

            connection.inputStream.use { input ->
                targetFile.outputStream().use { output ->
                    input.copyTo(output)
                }
            }
        } finally {
            connection.disconnect()
        }

        val actualSha = sha256(targetFile)
        if (!actualSha.equals(update.manifest.release.sha256, ignoreCase = true)) {
            targetFile.delete()
            throw IllegalStateException(
                "SHA-256 mismatch. Expected ${update.manifest.release.sha256}, got $actualSha."
            )
        }

        return targetFile
    }

    private fun buildManifestUrl(config: ArgusAppUpdateConfig): String {
        val baseUrl = config.baseUrl.trim().trimEnd('/')
        val slug = config.slug.trim()
        return "$baseUrl/api/apps/$slug/manifest?platform=${config.platform}&channel=${config.channel}"
    }

    private fun openConnection(url: String): HttpURLConnection =
        (URL(url).openConnection() as HttpURLConnection).apply {
            connectTimeout = 15_000
            readTimeout = 60_000
            setRequestProperty("Accept", "application/json")
        }

    private fun parseManifest(body: String): ArgusAppManifest {
        val root = JSONObject(body)
        val releaseJson = root.getJSONObject("release")
        return ArgusAppManifest(
            slug = root.getString("slug"),
            name = root.getString("name"),
            summary = root.getString("summary"),
            description = root.getString("description"),
            androidPackageName = root.optString("androidPackageName").ifBlank { null },
            repositoryUrl = root.optString("repositoryUrl").ifBlank { null },
            homepageUrl = root.optString("homepageUrl").ifBlank { null },
            release = ArgusPublishedRelease(
                id = releaseJson.getString("id"),
                version = releaseJson.getString("version"),
                channel = releaseJson.getString("channel"),
                platform = releaseJson.getString("platform"),
                packageKind = releaseJson.getString("packageKind"),
                androidVersionCode = releaseJson.takeIf { it.has("androidVersionCode") && !it.isNull("androidVersionCode") }
                    ?.getLong("androidVersionCode"),
                downloadPath = releaseJson.getString("downloadPath"),
                originalFileName = releaseJson.getString("originalFileName"),
                contentType = releaseJson.getString("contentType"),
                packageSizeBytes = releaseJson.getLong("packageSizeBytes"),
                sha256 = releaseJson.getString("sha256"),
                publishedAt = releaseJson.getString("publishedAt"),
                notes = releaseJson.optString("notes").ifBlank { null }
            )
        )
    }

    private fun HttpURLConnection.useJsonBody(block: (statusCode: Int, body: String) -> ArgusAppManifest?): ArgusAppManifest? =
        try {
            val statusCode = responseCode
            val stream = if (statusCode >= 400) errorStream else inputStream
            val body = stream?.bufferedReader()?.use { it.readText() }.orEmpty()
            block(statusCode, body)
        } finally {
            disconnect()
        }

    private fun sha256(file: File): String {
        val digest = MessageDigest.getInstance("SHA-256")
        file.inputStream().use { input ->
            val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
            while (true) {
                val read = input.read(buffer)
                if (read <= 0) {
                    break
                }
                digest.update(buffer, 0, read)
            }
        }
        return digest.digest().joinToString(separator = "") { byte -> "%02x".format(byte) }
    }
}
