/*
 * Copyright (C) 2020 Conny Duck
 *
 * This file is part of Pixelcat.
 *
 * Pixelcat is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Pixelcat is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package at.connyduck.pixelcat.db

import android.util.Log
import at.connyduck.pixelcat.db.entitity.AccountAuthData
import at.connyduck.pixelcat.db.entitity.AccountEntity
import at.connyduck.pixelcat.model.Account

/**
 * This class caches the account database and handles all account related operations
 */

private const val TAG = "AccountManager"

class AccountManager(db: AppDatabase) {

    private var activeAccount: AccountEntity? = null

    private var accounts: MutableList<AccountEntity> = mutableListOf()
    private val accountDao: AccountDao = db.accountDao()

    suspend fun activeAccount(): AccountEntity? {
        if (activeAccount == null) {
            accounts = accountDao.loadAll().toMutableList()

            activeAccount = accounts.find { acc ->
                acc.isActive
            }
        }
        return activeAccount
    }

    /**
     * Adds a new empty account and makes it the active account.
     * More account information has to be added later with [updateActiveAccount]
     * or the account wont be saved to the database.
     * @param domain the domain of the accounts instance
     * @param authData the auth data of the new account
     */
    suspend fun addAccount(domain: String, authData: AccountAuthData) {

        activeAccount?.let {
            it.isActive = false
            Log.d(TAG, "addAccount: saving account with id " + it.id)

            accountDao.insertOrReplace(it)
        }

        val maxAccountId = accounts.maxByOrNull { it.id }?.id ?: 0
        val newAccountId = maxAccountId + 1
        activeAccount = AccountEntity(
            id = newAccountId,
            domain = domain,
            auth = authData,
            isActive = true
        )
    }

    /**
     * Saves an already known account to the database.
     * New accounts must be created with [addAccount]
     * @param account the account to save
     */
    suspend fun saveAccount(account: AccountEntity) {
        if (account.id != 0L) {
            Log.d(TAG, "saveAccount: saving account with id " + account.id)
            accountDao.insertOrReplace(account)
        }
    }

    /**
     * Logs the current account out by deleting all data of the account.
     * @return the new active account, or null if no other account was found
     */
    suspend fun logActiveAccountOut(): AccountEntity? {

        if (activeAccount == null) {
            return null
        } else {
            accounts.remove(activeAccount!!)
            accountDao.delete(activeAccount!!)

            if (accounts.size > 0) {
                accounts[0].isActive = true
                activeAccount = accounts[0]
                Log.d(TAG, "logActiveAccountOut: saving account with id " + accounts[0].id)
                accountDao.insertOrReplace(accounts[0])
            } else {
                activeAccount = null
            }
            return activeAccount
        }
    }

    /**
     * updates the current account with new information from the api and saves it in the database
     * @param account the [Account] object returned from the api
     */
    suspend fun updateActiveAccount(account: Account) {
        activeAccount?.let {
            it.accountId = account.id
            it.username = account.username
            it.displayName = account.name
            it.profilePictureUrl = account.avatar

            Log.d(TAG, "updateActiveAccount: saving account with id " + it.id)
            it.id = accountDao.insertOrReplace(it)

            val accountIndex = accounts.indexOf(it)

            if (accountIndex != -1) {
                // in case the user was already logged in with this account, remove the old information
                accounts.removeAt(accountIndex)
                accounts.add(accountIndex, it)
            } else {
                accounts.add(it)
            }
        }
    }

    /**
     * changes the active account
     * @param accountId the database id of the new active account
     */
    suspend fun setActiveAccount(accountId: Long) {

        activeAccount?.let {
            Log.d(TAG, "setActiveAccount: saving account with id " + it.id)
            it.isActive = false
            saveAccount(it)
        }

        activeAccount = accounts.find { acc ->
            acc.id == accountId
        }

        activeAccount?.let {
            it.isActive = true
            accountDao.insertOrReplace(it)
        }
    }

    /**
     * @return an immutable list of all accounts in the database
     */
    suspend fun getAllAccounts(): List<AccountEntity> {

        // TODO only load accounts on demand
        accounts = accountDao.loadAll().toMutableList()

        activeAccount = accounts.find { acc ->
            acc.isActive
        }

        return accounts.toList()
    }

    /**
     * Finds an account by its database id
     * @param accountId the id of the account
     * @return the requested account or null if it was not found
     */
    fun getAccountById(accountId: Long): AccountEntity? {
        return accounts.find { acc ->
            acc.id == accountId
        }
    }
}
