package bou.amine.apps.readerforselfossv2.rest

import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.model.StatusAndData
import bou.amine.apps.readerforselfossv2.model.SuccessResponse
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import io.github.aakira.napier.Napier
import io.ktor.client.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.cache.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.cookies.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json

class SelfossApi(private val appSettingsService: AppSettingsService) {

    var client = createHttpClient()

    private fun createHttpClient(): HttpClient {
        val client = HttpClient {
            install(ContentNegotiation) {
                install(HttpCache)
                json(Json {
                    prettyPrint = true
                    isLenient = true
                    ignoreUnknownKeys = true
                })
            }
            install(Logging) {
                logger = object : Logger {
                    override fun log(message: String) {
                        Napier.d(message, tag = "LogApiCalls")
                    }
                }
                level = LogLevel.INFO
            }
            install(HttpTimeout) {
                requestTimeoutMillis = appSettingsService.getApiTimeout()
            }
            install(HttpCookies)
            install(HttpRequestRetry) {
                maxRetries = 2
                retryIf { _, response ->
                    response.status == HttpStatusCode.Forbidden && shouldHavePostLogin() && hasLoginInfo()
                }
                modifyRequest {
                    Napier.i("Will modify", tag = "HttpSend")
                    CoroutineScope(Dispatchers.Main).launch {
                        Napier.i("Will login", tag = "HttpSend")
                        this@SelfossApi.login()
                        Napier.i("Did login", tag = "HttpSend")
                    }
                }
            }
            expectSuccess = false
        }

        return client
    }

    fun url(path: String) =
        "${appSettingsService.getBaseUrl()}$path"

    fun refreshLoginInformation() {
        appSettingsService.refreshApiSettings()
        client = createHttpClient()
    }

    // Api version was introduces after the POST login, so when there is a version, it should be available
    private fun shouldHavePostLogin() = appSettingsService.getApiVersion() != -1
    private fun hasLoginInfo() = appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword().isNotEmpty()

    suspend fun login(): SuccessResponse =
        if (appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword().isNotEmpty()) {
            if (shouldHavePostLogin()) {
                postLogin()
            } else {
                getLogin()
            }
        } else {
            SuccessResponse(true)
        }

    private suspend fun getLogin() = maybeResponse(client.tryToGet(url("/login")) {
        parameter("username", appSettingsService.getUserName())
        parameter("password", appSettingsService.getPassword())
    })

    private suspend fun postLogin() = maybeResponse(client.tryToPost(url("/login")) {
        parameter("username", appSettingsService.getUserName())
        parameter("password", appSettingsService.getPassword())
    })

    private fun shouldHaveNewLogout() = appSettingsService.getApiVersion() >= 5 // We are missing 4.1.0
    suspend fun logout(): SuccessResponse =
        if (shouldHaveNewLogout()) {
            doLogout()
        } else {
            maybeLogoutIfAvailable()
        }

    private suspend fun maybeLogoutIfAvailable() = responseOrSuccessIf404(client.tryToGet(url("/logout")))

    private suspend fun doLogout() = maybeResponse(client.tryToDelete(url("/api/session/current")))

    suspend fun getItems(
        type: String,
        offset: Int,
        tag: String?,
        source: Long?,
        search: String?,
        updatedSince: String?,
        items: Int? = null
    ): StatusAndData<List<SelfossModel.Item>> =
        bodyOrFailure(client.tryToGet(url("/items")) {
            if (!shouldHavePostLogin()) {
                parameter("username", appSettingsService.getUserName())
                parameter("password", appSettingsService.getPassword())
            }
            parameter("type", type)
            parameter("tag", tag)
            parameter("source", source)
            parameter("search", search)
            parameter("updatedsince", updatedSince)
            parameter("items", items ?: appSettingsService.getItemsNumber())
            parameter("offset", offset)
        })

    suspend fun getItemsWithoutCatch(): StatusAndData<List<SelfossModel.Item>> =
        bodyOrFailure(client.get(url("/items")) {
            if (!shouldHavePostLogin()) {
                parameter("username", appSettingsService.getUserName())
                parameter("password", appSettingsService.getPassword())
            }
            parameter("type", "all")
            parameter("items", 1)
        })

    suspend fun stats(): StatusAndData<SelfossModel.Stats> =
        bodyOrFailure(client.tryToGet(url("/stats")) {
            if (!shouldHavePostLogin()) {
                parameter("username", appSettingsService.getUserName())
                parameter("password", appSettingsService.getPassword())
            }
        })

    suspend fun tags(): StatusAndData<List<SelfossModel.Tag>> =
        bodyOrFailure(client.tryToGet(url("/tags")) {
            if (!shouldHavePostLogin()) {
                parameter("username", appSettingsService.getUserName())
                parameter("password", appSettingsService.getPassword())
            }
        })

    suspend fun update(): StatusAndData<String> =
        bodyOrFailure(client.tryToGet(url("/update")) {
            if (!shouldHavePostLogin()) {
                parameter("username", appSettingsService.getUserName())
                parameter("password", appSettingsService.getPassword())
            }
        })

    suspend fun spouts(): StatusAndData<Map<String, SelfossModel.Spout>> =
        bodyOrFailure(client.tryToGet(url("/sources/spouts")) {
            if (!shouldHavePostLogin()) {
                parameter("username", appSettingsService.getUserName())
                parameter("password", appSettingsService.getPassword())
            }
        })

    suspend fun sources(): StatusAndData<ArrayList<SelfossModel.Source>> =
        bodyOrFailure(client.tryToGet(url("/sources/list")) {
            if (!shouldHavePostLogin()) {
                parameter("username", appSettingsService.getUserName())
                parameter("password", appSettingsService.getPassword())
            }
        })

    suspend fun version(): StatusAndData<SelfossModel.ApiVersion> =
        bodyOrFailure(client.tryToGet(url("/api/about")))

    suspend fun markAsRead(id: String): SuccessResponse =
        maybeResponse(client.tryToPost(url("/mark/$id")) {
            if (!shouldHavePostLogin()) {
                parameter("username", appSettingsService.getUserName())
                parameter("password", appSettingsService.getPassword())
            }
        })

    suspend fun unmarkAsRead(id: String): SuccessResponse =
        maybeResponse(client.tryToPost(url("/unmark/$id")) {
            if (!shouldHavePostLogin()) {
                parameter("username", appSettingsService.getUserName())
                parameter("password", appSettingsService.getPassword())
            }
        })

    suspend fun starr(id: String): SuccessResponse =
        maybeResponse(client.tryToPost(url("/starr/$id")) {
            if (!shouldHavePostLogin()) {
                parameter("username", appSettingsService.getUserName())
                parameter("password", appSettingsService.getPassword())
            }
        })

    suspend fun unstarr(id: String): SuccessResponse =
        maybeResponse(client.tryToPost(url("/unstarr/$id")) {
            if (!shouldHavePostLogin()) {
                parameter("username", appSettingsService.getUserName())
                parameter("password", appSettingsService.getPassword())
            }
        })

    suspend fun markAllAsRead(ids: List<String>): SuccessResponse =
        maybeResponse(client.tryToSubmitForm(
            url = url("/mark"),
            formParameters = Parameters.build {
                if (!shouldHavePostLogin()) {
                    append("username", appSettingsService.getUserName())
                    append("password", appSettingsService.getPassword())
                }
                ids.map { append("ids[]", it) }
            }
        ))

    suspend fun createSourceForVersion(
        title: String,
        url: String,
        spout: String,
        tags: String,
        filter: String
    ): SuccessResponse =
        maybeResponse(
            if (appSettingsService.getApiVersion() > 1) {
                createSource("tags[]", title, url, spout, tags, filter)
            } else {
                createSource("tags", title, url, spout, tags, filter)
            }
        )

    private suspend fun createSource(
        tagsParamName: String,
        title: String,
        url: String,
        spout: String,
        tags: String,
        filter: String
    ): HttpResponse? =
        client.tryToSubmitForm(
            url = url("/source"),
            formParameters = Parameters.build {
                if (!shouldHavePostLogin()) {
                    append("username", appSettingsService.getUserName())
                    append("password", appSettingsService.getPassword())
                }
                append("title", title)
                append("url", url)
                append("spout", spout)
                append(tagsParamName, tags)
                append("filter", filter)
            }
        )

    suspend fun deleteSource(id: Int): SuccessResponse =
        maybeResponse(client.tryToDelete(url("/source/$id")) {
            if (!shouldHavePostLogin()) {
                parameter("username", appSettingsService.getUserName())
                parameter("password", appSettingsService.getPassword())
            }
        })
}