package fdroidbuildstatus.adapter.fdroid

import de.storchp.fdroidbuildstatus.adapter.ApiResponse
import de.storchp.fdroidbuildstatus.adapter.fdroid.BuildResult
import de.storchp.fdroidbuildstatus.adapter.fdroid.FailedBuildItem
import de.storchp.fdroidbuildstatus.adapter.fdroid.FdroidClient
import de.storchp.fdroidbuildstatus.adapter.fdroid.Index
import de.storchp.fdroidbuildstatus.adapter.fdroid.Index.LocalizedName
import de.storchp.fdroidbuildstatus.adapter.fdroid.MissingBuildItem
import de.storchp.fdroidbuildstatus.adapter.fdroid.PublishedVersions
import de.storchp.fdroidbuildstatus.adapter.fdroid.RunningResult
import de.storchp.fdroidbuildstatus.adapter.fdroid.SuccessBuildIdItem
import de.storchp.fdroidbuildstatus.adapter.fdroid.UpdateResult
import de.storchp.fdroidbuildstatus.adapter.fdroid.WebsiteBuildStatus
import fdroidbuildstatus.utils.TestUtils
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.assertj.core.api.Assertions.assertThat
import org.awaitility.Awaitility.await
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.io.BufferedReader
import java.io.Reader
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicReference

internal class FdroidClientTest {
    private lateinit var server: MockWebServer
    private lateinit var client: FdroidClient

    @BeforeEach
    fun setUp() {
        server = MockWebServer()
        server.start()
        val baseUrl = server.url("/")
        client = FdroidClient(baseUrl.toString())
    }

    @AfterEach
    fun teardown() {
        server.shutdown()
    }

    @Test
    fun callGetFinishedBuildRun() {
        server.enqueue(MockResponse().setBody(TestUtils.fromFile("build.json")))
        val response = AtomicReference<ApiResponse<BuildResult?>>()
        client.getBuild { apiResponse -> response.set(apiResponse) }
        await().atMost(1, TimeUnit.SECONDS).until { response.get() != null }
        assertThat(server.takeRequest().path).isEqualTo("/repo/status/build.json")
        val buildRun = response.get()!!.value()
        assertThat(buildRun).isNotNull
        assertThat(buildRun?.startTimestamp).isEqualTo(1665726365000L)
        assertThat(buildRun?.endTimestamp).isEqualTo(1665833139602L)
        assertThat(buildRun?.subcommand).isEqualTo("build")
        assertThat(buildRun?.fdroiddata?.commitId)
            .isEqualTo("c9a65de6896e0f9320187a2aa1ead680bcc0f4c6")
        assertThat(buildRun?.getBuildItems()).containsExactlyInAnyOrder(
            FailedBuildItem("org.epstudios.epmobile", 76),
            FailedBuildItem("org.btcmap", 22),
            SuccessBuildIdItem("org.epstudios.epmobile", 77),
            SuccessBuildIdItem("org.btcmap", 17)
        )
    }

    @Test
    fun callGetRunningBuildRunWithEmptyBuild() {
        server.enqueue(MockResponse().setBody(TestUtils.fromFile("running_build_empty.json")))
        val response = AtomicReference<ApiResponse<RunningResult?>>()
        client.getRunning { apiResponse -> response.set(apiResponse) }
        await().atMost(1, TimeUnit.SECONDS).until { response.get() != null }
        assertThat(server.takeRequest().path).isEqualTo("/repo/status/running.json")
        val result = response.get()!!.value()
        assertThat(result?.startTimestamp).isEqualTo(1665901087000L)
        assertThat(result?.endTimestamp).isEqualTo(0L)
        assertThat(result?.subcommand).isEqualTo("build")
        assertThat(result?.fdroiddata?.commitId).isEqualTo("74a44c1596872bb898ae0f1fb865776006616dd3")
        assertThat(result).isInstanceOf(BuildResult::class.java)
        val buildResult = result as BuildResult
        assertThat(buildResult.failedBuildItems()).isEmpty()
        assertThat(buildResult.successfulBuildIds).isEmpty()
        assertThat(buildResult.allBuilds()).isEmpty()
    }

    @Test
    fun callGetRunningBuildRunWithDeployCommand() {
        server.enqueue(MockResponse().setBody(TestUtils.fromFile("running_deploy.json")))
        val response = AtomicReference<ApiResponse<RunningResult?>>()
        client.getRunning { apiResponse -> response.set(apiResponse) }
        await().atMost(1, TimeUnit.SECONDS).until { response.get() != null }
        assertThat(server.takeRequest().path)
            .isEqualTo("/repo/status/running.json")
        val result = response.get()!!.value()
        assertThat(result).isNotNull
        assertThat(result?.startTimestamp).isEqualTo(1665894966000L)
        assertThat(result?.endTimestamp).isEqualTo(1665895914221L)
        assertThat(result?.subcommand).isEqualTo("deploy")
        assertThat(result?.fdroiddata?.commitId)
            .isEqualTo("49c4a127bb64e8f22ec35dc7b6006586e6a25786")
        assertThat(result).isNotInstanceOf(BuildResult::class.java)
    }

    @Test
    fun callGetRunningBuildRunWithUpdateCommand() {
        server.enqueue(MockResponse().setBody(TestUtils.fromFile("running_update.json")))
        val response = AtomicReference<ApiResponse<RunningResult?>>()
        client.getRunning { apiResponse -> response.set(apiResponse) }
        await().atMost(1, TimeUnit.SECONDS).until { response.get() != null }
        assertThat(server.takeRequest().path).isEqualTo("/repo/status/running.json")
        val result = response.get()!!.value()
        assertThat(result).isNotNull
        assertThat(result?.startTimestamp).isEqualTo(1664958843000L)
        assertThat(result?.endTimestamp).isEqualTo(1664979743515L)
        assertThat(result?.subcommand).isEqualTo("update")
        assertThat(result?.fdroiddata?.commitId)
            .isEqualTo("5af8169594303c3c9ca30b672ee12d5b3933250a")
        assertThat(result).isInstanceOf(UpdateResult::class.java)
        val updateResult = result as UpdateResult
        assertThat(updateResult.missingBuildItems).containsExactlyInAnyOrder(
            MissingBuildItem("com.espruino.gadgetbridge.banglejs", 216),
            MissingBuildItem("com.standardnotes", 3000518)
        )
        assertThat(updateResult.archivePolicy0).containsExactlyInAnyOrder(
            "vu.de.urpool.quickdroid",
            "yellr.net.yellr_android"
        )
        assertThat(updateResult.disabled).containsExactlyInAnyOrder(
            "me.lucky.catcher",
            "me.lucky.reactor"
        )
        assertThat(updateResult.needsUpdate).containsExactlyInAnyOrder(
            "wb.receiptspro",
            "xyz.zedler.patrick.grocy"
        )
        assertThat(updateResult.noPackages).containsExactlyInAnyOrder(
            "org.owntracks.android",
            "security.pEp"
        )
        assertThat(updateResult.noUpdateCheck).containsExactlyInAnyOrder(
            "za.co.neilson.alarm",
            "zen.meditation.android"
        )
    }

    @Test
    fun callGetUpdate() {
        server.enqueue(MockResponse().setBody(TestUtils.fromFile("update.json")))
        val response = AtomicReference<ApiResponse<UpdateResult?>>()
        client.getUpdate { apiResponse -> response.set(apiResponse) }
        await().atMost(1, TimeUnit.SECONDS).until { response.get() != null }
        assertThat(server.takeRequest().path).isEqualTo("/repo/status/update.json")
        val update = response.get()!!.value()
        assertThat(update).isNotNull
        assertThat(update?.needsUpdate)
            .containsExactlyInAnyOrder("wb.receiptspro", "xyz.zedler.patrick.grocy")
        assertThat(update?.disabled)
            .containsExactlyInAnyOrder("me.lucky.catcher", "me.lucky.reactor")
        assertThat(update?.archivePolicy0).containsExactlyInAnyOrder(
            "vu.de.urpool.quickdroid",
            "yellr.net.yellr_android"
        )
        assertThat(update?.noPackages).containsExactlyInAnyOrder(
            "org.owntracks.android",
            "security.pEp"
        )
        assertThat(update?.noUpdateCheck).containsExactlyInAnyOrder(
            "za.co.neilson.alarm",
            "zen.meditation.android"
        )
    }

    @Test
    fun callGetWebsiteBuildStatus() {
        server.enqueue(MockResponse().setBody(TestUtils.fromFile("deploy-to-f-droid.org.json")))
        val response = AtomicReference<ApiResponse<WebsiteBuildStatus?>>()
        client.getWebsiteBuildStatus { apiResponse -> response.set(apiResponse) }
        await().atMost(1, TimeUnit.SECONDS).until { response.get() != null }
        assertThat(server.takeRequest().path)
            .isEqualTo("/repo/status/deploy-to-f-droid.org.json")
        val websiteBuildStatus = response.get()!!.value()
        assertThat(websiteBuildStatus).isNotNull
        assertThat(websiteBuildStatus?.startTimestamp).isEqualTo(1671715726000L)
        assertThat(websiteBuildStatus?.endTimestamp).isEqualTo(1671663330000L)
        assertThat(websiteBuildStatus?.commandLine)
            .containsExactly("/home/fdroid/deploy-to-f-droid.org")
    }

    @Test
    fun callGetIndex() {
        server.enqueue(MockResponse().setBody(TestUtils.fromFile("index-v1.jar")))
        val response = client.getIndex()
        assertThat(server.takeRequest().path).isEqualTo("/repo/index-v1.jar")
        val index = response.value()
        assertThat(index).isNotNull
        assertThat(index?.apps).contains(
            Index.App(
                null,
                "de.storchp.fdroidbuildstatus.nightly",
                "https://codeberg.org/pstorch/F-Droid_Build_Status",
                mapOf(
                    "de" to LocalizedName(),
                    "en-US" to LocalizedName("F-Droid Build Status (Nightly)"),
                    "fr" to LocalizedName(),
                    "he" to LocalizedName(),
                    "it" to LocalizedName(),
                    "tr" to LocalizedName(),
                    "zh-CN" to LocalizedName()
                )
            ),
            Index.App(
                null,
                "de.bahnhoefe.deutschlands.bahnhofsfotos.nightly",
                "https://github.com/RailwayStations/RSAndroidApp",
                mapOf(
                    "de-DE" to LocalizedName(),
                    "en-US" to LocalizedName("Railway station photos (Nightly)")
                )
            )
        )
    }

    @Test
    fun callGetBuildLog() {
        server.enqueue(
            MockResponse().setBody(TestUtils.fromFile("de.storchp.fdroidbuildstatus_36.log.gz"))
                .setHeader("content-encoding", "gzip")
        )
        val response = AtomicReference<ApiResponse<Reader?>>()
        client.getBuildLog("de.storchp.fdroidbuildstatus", 36) { apiResponse ->
            response.set(apiResponse)
        }
        await().atMost(1, TimeUnit.SECONDS).until { response.get() != null }
        assertThat(server.takeRequest().path).isEqualTo("/repo/de.storchp.fdroidbuildstatus_36.log.gz")
        val body = response.get()!!.value()
        val reader = BufferedReader(body)
        assertThat(reader.readLines()).hasSize(179)
    }

    @Test
    fun callGetBuildLogNotFound() {
        server.enqueue(MockResponse().setResponseCode(404))
        val response = AtomicReference<ApiResponse<Reader?>>()
        client.getBuildLog("de.storchp.fdroidbuildstatus", 36) { apiResponse ->
            response.set(apiResponse)
        }
        await().atMost(1, TimeUnit.SECONDS).until { response.get() != null }
        assertThat(server.takeRequest().path)
            .isEqualTo("/repo/de.storchp.fdroidbuildstatus_36.log.gz")
        assertThat(response.get()!!.status()).isEqualTo(ApiResponse.Status.NOT_FOUND)
        assertThat(response.get()!!.value()).isNull()
    }

    @Test
    fun callGetPublishedVersions() {
        server.enqueue(MockResponse().setBody(TestUtils.fromFile("published_versions.json")))
        val response = AtomicReference<ApiResponse<PublishedVersions?>>()
        client.getPublishedVersions("de.bahnhoefe.deutschlands.bahnhofsfotos") { apiResponse ->
            response.set(apiResponse)
        }
        await().atMost(1, TimeUnit.SECONDS).until { response.get() != null }
        assertThat(server.takeRequest().path)
            .isEqualTo("/api/v1/packages/de.bahnhoefe.deutschlands.bahnhofsfotos")
        val publishedVersions = response.get()!!.value()
        assertThat(publishedVersions).isNotNull
        assertThat(publishedVersions?.packageName)
            .isEqualTo("de.bahnhoefe.deutschlands.bahnhofsfotos")
        assertThat(publishedVersions?.suggestedVersionCode).isEqualTo("67")
        assertThat(publishedVersions?.packages).containsExactlyInAnyOrder(
            PublishedVersions.PublishedPackage("13.4.0", 67),
            PublishedVersions.PublishedPackage("13.3.0", 66),
            PublishedVersions.PublishedPackage("13.2.0", 65)
        )
    }

    @Test
    fun callGetPublishedVersionsNotFound() {
        server.enqueue(MockResponse().setResponseCode(404))
        val response = AtomicReference<ApiResponse<PublishedVersions?>>()
        client.getPublishedVersions("de.bahnhoefe.deutschlands.bahnhofsfotos") { apiResponse ->
            response.set(apiResponse)
        }
        await().atMost(1, TimeUnit.SECONDS).until { response.get() != null }
        assertThat(server.takeRequest().path)
            .isEqualTo("/api/v1/packages/de.bahnhoefe.deutschlands.bahnhofsfotos")
        assertThat(response.get()!!.status()).isEqualTo(ApiResponse.Status.NOT_FOUND)
        assertThat(response.get()!!.value()).isNull()
    }
}