package theorycrafter.esi

import eve.data.DroneType
import eve.data.EveData
import eve.data.EveItemType
import eve.data.ModuleType
import eve.esi.apis.DogmaApi
import eve.esi.apis.MarketApi
import eve.esi.apis.SkillsApi
import eve.esi.apis.StatusApi
import eve.esi.models.GetCharactersCharacterIdSkillsOk
import eve.esi.models.GetMarketsPrices200Ok
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.ensureActive
import okhttp3.OkHttpClient
import theorycrafter.Theorycrafter
import theorycrafter.fits.mutatedWith
import theorycrafter.utils.asOkHttpDispatcher
import theorycrafter.utils.setHeader
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toJavaDuration


/**
 * The User-Agent we use for ESI requests.
 */
private val USER_AGENT = "${Theorycrafter.AppName} ${Theorycrafter.Version}"


/**
 * The common HTTP client for ESI (and EVE SSO).
 */
val ESI_HTTP_CLIENT = OkHttpClient.Builder()
    .callTimeout(10.seconds.toJavaDuration())
    .dispatcher(Dispatchers.IO.asOkHttpDispatcher())
    .setHeader("User-Agent", USER_AGENT)
    .build()


/**
 * Returns an [OkHttpClient] that will authenticate using the given [EveSsoTokens].
 */
private fun authenticatedClient(ssoTokens: EveSsoTokens): OkHttpClient {
    return ESI_HTTP_CLIENT.newBuilder()
        .setHeader("Authorization", "Bearer ${ssoTokens.accessToken}")
        .build()
}


/**
 * The API to retrieve game status.
 */
val StatusApi = StatusApi(client = ESI_HTTP_CLIENT)


/**
 * The API to retrieve Dogma information.
 */
val DogmaApi = DogmaApi(client = ESI_HTTP_CLIENT)


/**
 * The API to retrieve market information.
 */
val MarketApi = MarketApi(client = ESI_HTTP_CLIENT)


/**
 * Returns the API to retrieve skills with the given [EveSsoTokens].
 */
fun SkillsApi(ssoTokens: EveSsoTokens) = SkillsApi(client = authenticatedClient(ssoTokens))


/**
 * Retrieves a mutated item type.
 */
context(EveData)
suspend fun DogmaApi.getMutatedType(typeId: Int, itemId: Long): EveItemType? {
    val dynamicItemData = getDogmaDynamicItemsTypeIdItemId(itemId, typeId, null, null)
    currentCoroutineContext().ensureActive()

    val mutaplasmidId = dynamicItemData.mutatorTypeId
    val mutaplasmid = mutaplasmidById[mutaplasmidId] ?: return null
    val sourceTypeId = dynamicItemData.sourceTypeId
    val baseType = moduleTypeOrNull(sourceTypeId) ?: droneTypeOrNull(sourceTypeId) ?: return null

    val valueByAttribute = dynamicItemData.dogmaAttributes
        .mapNotNull {
            val attribute = attributes.getOrNull(it.attributeId) ?: return@mapNotNull null

            // The received value can be slightly out of range because of differences in computation and parsing.
            // To work around this, we coerce the received value into the valid range if it's slightly outside the range.
            val value = it.value.let { receivedValue ->
                // ESI sends us some irrelevant attributes like mass, radius for some reason
                if (!baseType.attributeValues.has(attribute))
                    return@mapNotNull null
                val baseValue = baseType.attributeValues.getDoubleValue(attribute.id)
                val range = mutaplasmid.attributeMutations[attribute.id]?.attributeValueRange(baseValue) ?: return@mapNotNull null
                val receivedDouble = receivedValue.toDouble()
                if (receivedDouble in range)
                    return@let receivedDouble

                val diff = if (receivedDouble > range.endInclusive)
                    receivedDouble - range.endInclusive
                else
                    range.start - receivedDouble

                val rangeSize = range.endInclusive - range.start
                if (diff * 10_000 < rangeSize)  // 10_000 is just a guess; may need to lower it
                    receivedDouble.coerceIn(range)
                else
                    receivedDouble  // This will fail a later bounds check, but so it should
            }

            attribute to value
        }
        .toMap()

    return when (baseType) {
        is ModuleType ->
            baseType.mutatedWith(
                mutaplasmid = mutaplasmid,
                valueByAttribute = valueByAttribute
            )
        is DroneType ->
            baseType.mutatedWith(
                mutaplasmid = mutaplasmid,
                valueByAttribute = valueByAttribute
            )
    }
}


/**
 * Retrieves the character's skills.
 */
fun SkillsApi.getCharacterSkills(characterId: Int): GetCharactersCharacterIdSkillsOk {
    return getCharactersCharacterIdSkills(characterId = characterId)
}


/**
 * Retrieves eve item prices.
 */
fun MarketApi.getAllItemPrices(): List<GetMarketsPrices200Ok> = getMarketsPrices()


/**
 * An ESI authorization scope.
 */
data class EsiAuthScope(val id: String)


/**
 * The list of ESI scopes we use (not a complete list).
 */
object EsiScopes {


    /**
     * The scope for reading character skills.
     */
    val ReadSkills = EsiAuthScope("esi-skills.read_skills.v1")


}