package de.storchp.fdroidbuildstatus.adapter.fdroid

import android.util.Log
import androidx.core.net.toUri
import com.google.gson.GsonBuilder
import de.storchp.fdroidbuildstatus.BuildConfig
import de.storchp.fdroidbuildstatus.adapter.ApiCallback
import de.storchp.fdroidbuildstatus.adapter.ApiResponse
import de.storchp.fdroidbuildstatus.adapter.fdroid.BaseCommandResultTypeAdapter.BaseCommandResultTypeAdapterFactory
import de.storchp.fdroidbuildstatus.utils.UserAgentInterceptor
import okhttp3.OkHttpClient
import okhttp3.ResponseBody
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.io.InputStreamReader
import java.io.Reader
import java.util.Date
import java.util.jar.JarEntry
import java.util.jar.JarInputStream

private val TAG = FdroidClient::class.java.simpleName
private const val DATA_FILE_NAME = "index-v1.json"

class FdroidClient(private val baseUrl: String) {
    private val api: FdroidApi
    private fun getLastModified(response: Response<out RunningResult?>): Date {
        var date = response.headers().getDate("Last-Modified")
        if (date == null) {
            date = Date() // fallback
        }
        return date
    }

    fun logUri(id: String, versionCode: Long) = "$baseUrl/repo/${id}_$versionCode.log.gz".toUri()

    init {
        val gson = GsonBuilder()
            .registerTypeAdapter(FailedBuilds::class.java, FailedBuilds.Deserializer())
            .registerTypeAdapter(MissingBuilds::class.java, MissingBuilds.Deserializer())
            .registerTypeAdapterFactory(BaseCommandResultTypeAdapterFactory())
        val loggingInterceptor = HttpLoggingInterceptor()
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC)
        val builder: OkHttpClient.Builder = OkHttpClient.Builder()
            .addInterceptor(UserAgentInterceptor())
        if (BuildConfig.DEBUG) {
            builder.addInterceptor(loggingInterceptor)
        }
        val retrofit = Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(builder.build())
            .addConverterFactory(GsonConverterFactory.create(gson.create()))
            .build()
        api = retrofit.create(FdroidApi::class.java)
    }

    fun getBuild(callback: ApiCallback<BuildResult>) {
        val call = api.build
        call.enqueue(object : Callback<BuildResult> {
            override fun onResponse(call: Call<BuildResult>, response: Response<BuildResult>) {
                val buildRun = response.body()
                if (response.isSuccessful && buildRun != null) {
                    buildRun.lastModified = getLastModified(response)
                    callback.onResponse(ApiResponse.success(buildRun))
                } else {
                    Log.e(TAG, "Error loading finished build status: " + response.message())
                    callback.onResponse(
                        ApiResponse.error(
                            response.code().toString() + " " + response.message()
                        )
                    )
                }
            }

            override fun onFailure(call: Call<BuildResult>, t: Throwable) {
                Log.e(TAG, "Error loading finished build status", t)
                callback.onResponse(ApiResponse.error(t.message))
            }
        })
    }

    fun getRunning(callback: ApiCallback<RunningResult>) {
        val call = api.running
        call.enqueue(object : Callback<RunningResult> {
            override fun onResponse(
                call: Call<RunningResult>,
                response: Response<RunningResult>
            ) {
                val buildRun = response.body()
                if (response.isSuccessful && buildRun != null) {
                    buildRun.lastModified = getLastModified(response)
                    callback.onResponse(ApiResponse.success(buildRun))
                } else {
                    Log.e(TAG, "Error loading running build status: " + response.message())
                    callback.onResponse(
                        ApiResponse.error(
                            response.code().toString() + " " + response.message()
                        )
                    )
                }
            }

            override fun onFailure(call: Call<RunningResult>, t: Throwable) {
                Log.e(TAG, "Error loading running build status", t)
                callback.onResponse(ApiResponse.error(t.message))
            }
        })
    }

    fun getIndex(): ApiResponse<Index?> {
        val response = api.index.execute()
        response.body().use { body ->
            if (response.isSuccessful && body != null) {
                try {
                    JarInputStream(body.byteStream(), true).use { jarFile ->
                        var indexEntry: JarEntry? = null
                        var entry = jarFile.nextJarEntry
                        do {
                            if (entry!!.name == DATA_FILE_NAME) {
                                indexEntry = entry
                            } else {
                                entry = jarFile.nextJarEntry
                            }
                        } while (indexEntry == null && entry != null)
                        val index = GsonBuilder().create()
                            .fromJson(InputStreamReader(jarFile), Index::class.java)
                        return ApiResponse.success(index)
                    }
                } catch (e: Exception) {
                    Log.e(TAG, "Loading index failed", e)
                    ApiResponse.error<Index?>(e.message)
                }
            }
        }
        return ApiResponse.error(
            response.code().toString() + " " + response.message()
        )
    }

    fun getUpdate(callback: ApiCallback<UpdateResult>) {
        val call = api.update
        call.enqueue(object : Callback<UpdateResult> {
            override fun onResponse(call: Call<UpdateResult>, response: Response<UpdateResult>) {
                val update = response.body()
                if (response.isSuccessful && update != null) {
                    update.lastModified = getLastModified(response)
                    callback.onResponse(ApiResponse.success(update))
                } else {
                    Log.e(TAG, "Error loading update: " + response.message())
                    callback.onResponse(
                        ApiResponse.error(
                            response.code().toString() + " " + response.message()
                        )
                    )
                }
            }

            override fun onFailure(call: Call<UpdateResult>, t: Throwable) {
                Log.e(TAG, "Error loading update", t)
                callback.onResponse(ApiResponse.error(t.message))
            }
        })
    }

    fun getPublishedVersions(id: String, callback: ApiCallback<PublishedVersions>) {
        val call = api.getPublishedPackage(id)
        call.enqueue(object : Callback<PublishedVersions> {
            override fun onResponse(
                call: Call<PublishedVersions>,
                response: Response<PublishedVersions>
            ) {
                val publishedVersions = response.body()
                if (response.isSuccessful && publishedVersions != null) {
                    Log.d(TAG, "loaded published versions")
                    callback.onResponse(
                        ApiResponse.success(
                            publishedVersions
                        )
                    )
                } else if (response.code() == 404 || publishedVersions == null) {
                    Log.w(TAG, "published versions not found for id $id")
                    callback.onResponse(
                        ApiResponse.error(
                            ApiResponse.Status.NOT_FOUND,
                            response.message()
                        )
                    )
                } else {
                    Log.e(
                        TAG,
                        "loading published versions for id " + id + " failed with: " + response.message()
                    )
                    callback.onResponse(
                        ApiResponse.error(
                            response.code().toString() + " " + response.message()
                        )
                    )
                }
            }

            override fun onFailure(call: Call<PublishedVersions>, t: Throwable) {
                Log.e(TAG, "loading published versions for id $id failed$t")
                callback.onResponse(ApiResponse.error(t.message))
            }
        })
    }

    fun getBuildLog(id: String, versionCode: Long, callback: ApiCallback<Reader>) {
        val call = api.getBuildLog(id, versionCode)
        call.enqueue(object : Callback<ResponseBody> {
            override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
                response.body().use { body ->
                    if (response.isSuccessful && body != null) {
                        Log.d(TAG, "loaded logfile: " + call.request().url)
                        callback.onResponse(ApiResponse.success(body.charStream()))
                    } else if (response.code() == 404 || body == null) {
                        Log.w(TAG, "logfile not found" + response.message())
                        callback.onResponse(
                            ApiResponse.error(
                                ApiResponse.Status.NOT_FOUND,
                                response.message()
                            )
                        )
                    } else {
                        Log.e(TAG, "loading logfile failed: " + response.message())
                        callback.onResponse(
                            ApiResponse.error(
                                response.code().toString() + " " + response.message()
                            )
                        )
                    }
                }
            }

            override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
                Log.e(TAG, "loading logfile failed", t)
                callback.onResponse(ApiResponse.error(t.message))
            }
        })
    }

    fun getWebsiteBuildStatus(callback: ApiCallback<WebsiteBuildStatus>) {
        val call = api.websiteBuildStatus
        call.enqueue(object : Callback<WebsiteBuildStatus> {
            override fun onResponse(
                call: Call<WebsiteBuildStatus>,
                response: Response<WebsiteBuildStatus>
            ) {
                val update = response.body()
                if (response.isSuccessful && update != null) {
                    callback.onResponse(ApiResponse.success(update))
                } else {
                    Log.e(TAG, "Error loading update: " + response.message())
                    callback.onResponse(
                        ApiResponse.error(
                            response.code().toString() + " " + response.message()
                        )
                    )
                }
            }

            override fun onFailure(call: Call<WebsiteBuildStatus>, t: Throwable) {
                Log.e(TAG, "Error loading update", t)
                callback.onResponse(ApiResponse.error(t.message))
            }
        })
    }

}