package com.phapps.elitedangerous.companion.data.repos

import android.arch.lifecycle.LiveData
import android.arch.lifecycle.MutableLiveData
import com.phapps.elitedangerous.companion.EdacApp
import com.phapps.elitedangerous.companion.EdacExecutors
import com.phapps.elitedangerous.companion.api.ApiResponse
import com.phapps.elitedangerous.companion.api.edc.EdcService
import com.phapps.elitedangerous.companion.api.edc.dtos.EdcProfileDto
import com.phapps.elitedangerous.companion.data.doa.CommanderDao
import com.phapps.elitedangerous.companion.data.doa.ShipDao
import com.phapps.elitedangerous.companion.data.doa.StarSystemDao
import com.phapps.elitedangerous.companion.data.entities.*
import com.phapps.elitedangerous.companion.domain.Resource
import com.phapps.elitedangerous.companion.ext.withRanks
import com.phapps.elitedangerous.companion.ext.withRanksAndProgress
import com.phapps.elitedangerous.companion.util.SharedPrefsHelper
import com.phapps.elitedangerous.edsm.EdsmClient
import com.phapps.elitedangerous.edsm.callbacks.GetCommanderRanksCallbacks
import com.phapps.elitedangerous.edsm.dto.CommanderDto
import kotlinx.coroutines.experimental.async
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.runBlocking
import timber.log.Timber
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class ProfileRepository @Inject constructor(private var executors: EdacExecutors,
        private var commanderDao: CommanderDao, private var shipDao: ShipDao,
        private var systemDao: StarSystemDao, private var prefs: SharedPrefsHelper) :
        IProfileRepository {

    override fun getProfile(name: String,
            forceRefresh: Boolean): LiveData<Resource<CommanderProfile>> {
        return object : WebBoundResource<CommanderProfile, EdcProfileDto>(executors) {
            override fun loadFromDb(): LiveData<CommanderProfile> {
                return commanderDao.getCommanderByName(name)
            }

            override fun shouldFetch(data: CommanderProfile?): Boolean {
                if (data == null) return true

                val now = Date().time
                return forceRefresh || now >= calculateStaleDate(data.lastUpdatedTimestamp)
            }

            override fun createCall(): LiveData<ApiResponse<EdcProfileDto>> {
                val liveData = MutableLiveData<ApiResponse<EdcProfileDto>>()

                launch {
                    liveData.postValue(EdcService(EdacApp.instance).getProfile())
                }

                return liveData
            }

            override fun saveCallResult(response: EdcProfileDto) {
                val newProfile = CommanderProfile().also { it ->
                    with(response) {
                        it.id = commander.id
                        it.name = name
                        it.credits = commander.credits
                        it.debt = commander.debt
                        it.alive = commander.alive
                        it.currentShipId = commander.currentShipId
                        it.lastUpdatedTimestamp = Date().time

                        it.ranks = runBlocking { createRanks(it) }
                    }
                }

                val ships = mutableListOf<Ship>()
                val stations = mutableListOf<Station>()
                val systems = mutableListOf<StarSystem>()
                val moduleSlots = mutableListOf<ModuleSlot>()

                // The current ship is the only ship populated with full details
                ships.add(Ship().also { ship ->

                    with(response.ship) {
                        newProfile.currentShipName = this.name

                        ship.id = this.id
                        ship.name = this.name
                        ship.free = this.free
                        ship.stationId = this.station.id
                        ship.starSystemId = this.starsystem.id
                        ship.oxygenRemaining = this.oxygenRemaining
                        ship.cockpitBreached = this.cockpitBreached
                        ship.alive = this.alive

                        this.health?.let {
                            ship.hullHealth = it.hull
                            ship.shieldHealth = it.shield
                            ship.shieldUp = it.shieldup
                            ship.integrity = it.integrity
                            ship.paintwork = it.paintwork
                        }

                        ship.hullValue = this.value.hull
                        ship.modulesValue = this.value.hull
                        ship.cargoValue = this.value.cargo
                        ship.totalValue = this.value.total
                        ship.unloanedValue = this.value.unloaned

                        this.modules?.let { map ->
                            for (entry in map) {
                                moduleSlots.add(ModuleSlot().also { ms ->
                                    ms.name = entry.key
                                })
                            }
                        }
                    }
                })

                // List of owned ships does not include all ship details.
                if (response.ships.isNotEmpty()) {
                    for (dto in response.ships) {
                        if (dto.id != response.ship.id) {
                            ships.add(Ship().also { ship ->
                                with(dto) {

                                    ship.id = this.id
                                    ship.name = this.name
                                    ship.free = this.free
                                    ship.stationId = this.station.id
                                    with(this.station) {

                                        stations.add(Station().also { s ->
                                            s.name = this.name
                                            s.id = this.id
                                        })
                                    }

                                    ship.starSystemId = this.starsystem.id

                                    with(this.starsystem) {

                                        systems.add(StarSystem().also { s ->
                                            s.id = this.id
                                            s.name = this.name
                                            s.address = this.systemaddress.toString()
                                        })
                                    }

                                    ship.alive = this.alive
                                }
                            })
                        }
                    }
                }

                val starport = Starport().also { s ->
                    with(response.lastStarport) {
                        s.id = this.id
                        s.name = this.name
                        s.faction = this.faction
                        s.minorFaction = this.minorfaction
                        s.services = StarportServices().also { services ->
                            with(this.services) {
                                services.dock = this.dock == "ok"
                                services.contacts = this.contacts == "ok"
                                services.cartographers = this.exploration == "ok"
                                services.refuel = this.refuel == "ok"
                                services.repair = this.repair == "ok"
                                services.outfitting = this.outfitting == "ok"
                                services.crewLounge = this.crewlounge == "ok"
                                services.powerPlay = this.powerplay == "ok"
                                services.searchAndRescue = this.searchrescue == "ok"
                                services.engineer = this.engineer == "ok"
                            }
                        }
                    }
                }

                commanderDao.insert(newProfile)
                systemDao.insert(*stations.toTypedArray())
                systemDao.insert(*systems.toTypedArray())
                systemDao.insert(starport)
                shipDao.insert(*ships.toTypedArray())
            }
        }.asLiveData()
    }

    private suspend fun createRanks(commanderProfile: CommanderProfile): Ranks {

        var ranks = Ranks.withRanks(commanderProfile.ranks.combatRank,
                commanderProfile.ranks.traderRank, commanderProfile.ranks.explorerRank,
                commanderProfile.ranks.cqcRank, commanderProfile.ranks.federationRank,
                commanderProfile.ranks.empireRank, commanderProfile.ranks.crime,
                commanderProfile.ranks.power, commanderProfile.ranks.service)

        async {
            EdsmClient.getInstance().getCommanderRanks(commanderProfile.name, prefs.getEdsmApiKey(),
                    object : GetCommanderRanksCallbacks {
                        override fun onSuccess(commander: CommanderDto) {

                            Timber.d("commander = $commander")
                            var combatProgress = commanderProfile.ranks.combatProgress
                            var tradeProgress = commanderProfile.ranks.traderProgress
                            var exploreProgress = commanderProfile.ranks.explorerProgress
                            var cqcProgress = commanderProfile.ranks.cqcProgress
                            var empireProgress = commanderProfile.ranks.empireProgress
                            var federationProgress = commanderProfile.ranks.federationProgress

                            if (commander.ranks != null) {
                                if (commanderProfile.ranks.combatRank == commander.ranks.combat) {
                                    combatProgress = commander.progress.combat
                                }

                                if (commanderProfile.ranks.traderRank == commander.ranks.trade) {
                                    tradeProgress = commander.ranks.trade
                                }

                                if (commanderProfile.ranks.explorerRank == commander.ranks.explore) {
                                    exploreProgress = commander.progress.explore
                                }

                                if (commanderProfile.ranks.cqcProgress == commander.ranks.cqc) {
                                    cqcProgress = commander.progress.cqc
                                }

                                if (commanderProfile.ranks.empireRank == commander.ranks.empire) {
                                    empireProgress = commander.progress.empire
                                }

                                if (commanderProfile.ranks.federationRank == commander.ranks.federation) {
                                    federationProgress = commander.progress.federation
                                }
                            }

                            ranks = Ranks.withRanksAndProgress(
                                    Pair(commanderProfile.ranks.combatRank, combatProgress),
                                    Pair(commanderProfile.ranks.traderRank, tradeProgress),
                                    Pair(commanderProfile.ranks.explorerRank, exploreProgress),
                                    Pair(commanderProfile.ranks.cqcRank, cqcProgress),
                                    Pair(commanderProfile.ranks.federationRank, federationProgress),
                                    Pair(commanderProfile.ranks.empireRank, empireProgress),
                                    commanderProfile.ranks.crime, commanderProfile.ranks.power,
                                    commanderProfile.ranks.service)
                        }

                        override fun onFail(message: String) {
                            Timber.w("Error fetching rank details from EDSM: $message")
                        }

                        override fun onError() {
                            Timber.w("Unable to communicate with EDSM servers to fetch Ranks.")
                        }
                    })
        }.await()

        return ranks
    }
}
