import { Client } from 'cozy-client'
import {
  CHALLENGETYPE_DOCTYPE,
  ECOGESTURE_DOCTYPE,
  USERCHALLENGE_DOCTYPE,
  USERPROFILE_DOCTYPE,
  EGL_DAY_DOCTYPE,
  EGL_MONTH_DOCTYPE,
  EGL_YEAR_DOCTYPE,
  ENEDIS_DAY_DOCTYPE,
  ENEDIS_MINUTE_DOCTYPE,
  ENEDIS_MONTH_DOCTYPE,
  ENEDIS_YEAR_DOCTYPE,
  GRDF_DAY_DOCTYPE,
  GRDF_HOUR_DOCTYPE,
  GRDF_MONTH_DOCTYPE,
  GRDF_YEAR_DOCTYPE,
} from 'doctypes'

import ChallengeDataManager from 'services/challengeDataManagerService'
import challengeTypeData from 'db/challengeTypeData.json'
import userChallengeData from 'db/userChallengeData.json'
import EcogestureDataManager from 'services/ecogestureDataManagerService'
import ecogestureData from 'db/ecogestureData.json'
import UserProfileDataManager from 'services/userProfileDataManagerService'
import userProfileData from 'db/userProfileData.json'
import KonnectorStatusService from 'services/konnectorStatusService'
import KonnectorService from 'services/konnectorService'
import { AccountService } from 'services/accountService'

import { hashFile } from 'utils/hash'
import {
  UserProfile,
  TypeChallenge,
  UserChallenge,
} from 'services/dataChallengeContracts'
import { FluidType } from 'enum/fluid.enum'
import { DateTime } from 'luxon'

export default class InitDataManager {
  private readonly _client: Client

  constructor(_client: Client) {
    this._client = _client
  }

  public async checkChallengeType(hash: string): Promise<boolean> {
    const hashChanllengeType = hashFile(challengeTypeData)
    const cdm = new ChallengeDataManager(this._client)
    const updm = new UserProfileDataManager(this._client)

    // Populate data if none challengeType exists
    const loadedChallengeTypes = await cdm.getAllChallengeTypeEntities()
    if (
      !loadedChallengeTypes ||
      (loadedChallengeTypes && loadedChallengeTypes.length === 0)
    ) {
      // Populate the doctype with data
      try {
        for (let i = 0; i <= challengeTypeData.length - 1; i++) {
          const result = await this._client.create(
            CHALLENGETYPE_DOCTYPE,
            challengeTypeData[i]
          )
          if (!result) {
            return false
          }
        }
        // Check of created document
        const checkCount = await cdm.getAllChallengeTypeEntities()
        if (
          !checkCount ||
          (checkCount && checkCount.length !== challengeTypeData.length)
        ) {
          return false
        }
        // Update userProfil with the hash
        const updatedUserProfil = await updm.updateUserProfile({
          challengeTypeHash: hashChanllengeType,
        })
        if (!updatedUserProfil) {
          return false
        }
      } catch (error) {
        console.log('Context error: ', error)
        return false
      }
      return true
    }

    // Update if the hash is not the same as the one from userprofile
    if (hash !== hashChanllengeType) {
      // Update the doctype
      try {
        // Deletion of all documents
        const resultDeletion = await cdm.deleteAllChallengeTypeEntities()
        if (!resultDeletion) {
          return false
        }
        // Population with the data
        const resultPopulation = await Promise.all(
          challengeTypeData.map(async challengeType => {
            await this._client.create(CHALLENGETYPE_DOCTYPE, challengeType)
          })
        )
        if (!resultPopulation) {
          return false
        }
        // Check of created document
        const checkCount = await cdm.getAllChallengeTypeEntities()
        if (
          !checkCount ||
          (checkCount && checkCount.length !== challengeTypeData.length)
        ) {
          return false
        }
        // Update userProfil with the hash
        const updatedUserProfil = await updm.updateUserProfile({
          challengeTypeHash: hashChanllengeType,
        })
        if (!updatedUserProfil) {
          return false
        }
      } catch (error) {
        console.log('Context error: ', error)
        return false
      }
      return true
    } else {
      // Doctype already up to date
      return true
    }
  }

  public async checkEcogesture(hash: string): Promise<boolean> {
    const hashEcogestureType = hashFile(ecogestureData)
    const edm = new EcogestureDataManager(this._client)
    const updm = new UserProfileDataManager(this._client)

    // Populate data if none ecogesture exists
    const loadedEcogestures = await edm.getAllEcogestures()
    if (
      !loadedEcogestures ||
      (loadedEcogestures && loadedEcogestures.length === 0)
    ) {
      // Populate the doctype with data
      try {
        for (let i = 0; i <= ecogestureData.length - 1; i++) {
          const result = await this._client.create(
            ECOGESTURE_DOCTYPE,
            ecogestureData[i]
          )
          if (!result) {
            return false
          }
        }

        // Check of created document based on count
        const checkCount = await edm.getAllEcogestures()
        if (
          !checkCount ||
          (checkCount && checkCount.length !== ecogestureData.length)
        ) {
          return false
        }
        // Update userProfil with the hash
        const updatedUserProfil = await updm.updateUserProfile({
          ecogestureHash: hashEcogestureType,
        })
        if (!updatedUserProfil) {
          return false
        }
      } catch (error) {
        console.log('Context error: ', error)
        return false
      }
      return true
    }

    // Update if the hash is not the same as the one from userprofile
    if (hash !== hashEcogestureType) {
      // Update the doctype
      try {
        // Deletion of all documents
        const resultDeletion = await edm.deleteAllEcogestures()
        if (!resultDeletion) {
          return false
        }
        // Population with the data
        const resultPopulation = await Promise.all(
          ecogestureData.map(async ecogesture => {
            await this._client.create(ECOGESTURE_DOCTYPE, ecogesture)
          })
        )
        if (!resultPopulation) {
          return false
        }
        // Check of created document based on count
        const checkCount = await edm.getAllEcogestures()
        if (
          !checkCount ||
          (checkCount && checkCount.length !== ecogestureData.length)
        ) {
          return false
        }
        // Update userProfil with the hash
        const updatedUserProfil = await updm.updateUserProfile({
          ecogestureHash: hashEcogestureType,
        })
        if (!updatedUserProfil) {
          return false
        }
      } catch (error) {
        console.log('Context error: ', error)
        return false
      }
      return true
    } else {
      // Doctype already up to date
      return true
    }
  }

  /*
   * Check if userChallenge exist
   * If not, the userChallenge is created
   * sucess return: true
   * failure return: false
   */
  public async checkUserChallenge(): Promise<boolean> {
    const cdm = new ChallengeDataManager(this._client)
    const loadedUserChallenge = await cdm.getAllUserChallengeEntities()
    if (
      !loadedUserChallenge ||
      (loadedUserChallenge && loadedUserChallenge.length === 0)
    ) {
      try {
        // Population with the data
        await this._client.create(USERCHALLENGE_DOCTYPE, userChallengeData[0])
        // Check of created document
        const checkUserChallenge = await cdm.getAllUserChallengeEntities()
        if (!checkUserChallenge) {
          return false
        }
        return true
      } catch (error) {
        console.log('Context error: ', error)
        return false
      }
    } else {
      return true
    }
  }

  /*
   * Check if UserProfil exist
   * If not, the UserProfil is created
   * sucess return: userProfil
   * failure return: null
   */
  public async checkUserProfile(): Promise<UserProfile | null> {
    const updm = new UserProfileDataManager(this._client)
    const loadedUserProfile = await updm.getUserProfile()
    if (!loadedUserProfile) {
      try {
        // Population with the data
        await this._client.create(USERPROFILE_DOCTYPE, userProfileData[0])
        // Check of created document
        const checkUserProfile = await updm.getUserProfile()
        return checkUserProfile
      } catch (error) {
        console.log('Context error: ', error)
        return null
      }
    } else {
      return loadedUserProfile
    }
  }

  /*
   * Call each check function to init data
   * sucess return: true
   * failure return: false or null
   */
  public async initData(): Promise<boolean | null> {
    try {
      const resultUserProfile = await this.checkUserProfile()
      if (!resultUserProfile) {
        return false
      } else {
        console.log(
          '%c Context: UserProfile loaded ',
          'background: #222; color: white'
        )
      }
      const resultChallengeType = await this.checkChallengeType(
        resultUserProfile.challengeTypeHash
      )
      if (!resultChallengeType) {
        return false
      } else {
        console.log(
          '%c Context: ChallengeType loaded ',
          'background: #222; color: white'
        )
      }
      const resultEcogesture = await this.checkEcogesture(
        resultUserProfile.ecogestureHash
      )
      if (!resultEcogesture) {
        return false
      } else {
        console.log(
          '%c Context: Ecogesture loaded ',
          'background: #222; color: white'
        )
      }
      const resultUserChallenge = await this.checkUserChallenge()
      if (!resultUserChallenge) {
        return false
      } else {
        console.log(
          '%c Context: UserChallenge loaded',
          'background: #222; color: white'
        )
      }
      return true
    } catch (error) {
      console.log('Context error: ', error)
      return null
    }
  }

  /*
   * Check if FluidTypes exist
   * sucess return: FluidType[]
   * failure return: null
   */
  public async checkFluidTypes(): Promise<FluidType[] | null> {
    const kss = new KonnectorStatusService(this._client)
    try {
      const fluidtypes = await kss.getKonnectorAccountStatus()
      if (!fluidtypes) {
        return null
      }
      return fluidtypes
    } catch (error) {
      console.log('Context error: ', error)
      return null
    }
  }

  /*
   * Call a query with where clause to create the index if not exist
   */
  public async createIndex(doctype: string): Promise<any> {
    return await this._client.query(
      this._client
        .find(doctype)
        .where({
          year: {
            $lte: 9999,
          },
          month: {
            $lte: 12,
          },
          day: {
            $lte: 31,
          },
        })
        .limitBy(1)
    )
  }

  /*
   * create index for each Doctype
   * sucess return: true
   * failure return: null
   */
  public async initIndex(): Promise<boolean | null> {
    try {
      await this.createIndex(EGL_DAY_DOCTYPE)
      await this.createIndex(EGL_MONTH_DOCTYPE)
      await this.createIndex(EGL_YEAR_DOCTYPE)
      await this.createIndex(ENEDIS_DAY_DOCTYPE)
      await this.createIndex(ENEDIS_MINUTE_DOCTYPE)
      await this.createIndex(ENEDIS_MONTH_DOCTYPE)
      await this.createIndex(ENEDIS_YEAR_DOCTYPE)
      await this.createIndex(GRDF_DAY_DOCTYPE)
      await this.createIndex(GRDF_HOUR_DOCTYPE)
      await this.createIndex(GRDF_MONTH_DOCTYPE)
      await this.createIndex(GRDF_YEAR_DOCTYPE)
      await KonnectorService.createIndexKonnector(this._client)
      await AccountService.createIndexAccount(this._client)
      return true
    } catch (error) {
      console.log('Context error: ', error)
      return null
    }
  }

  /*
   * Search for a current challenge
   * and update it if found
   */
  public async checkCurrentChallenge(
    fluidTypes: FluidType[]
  ): Promise<UserChallenge | null> {
    try {
      // get the current challenge
      const cdm = new ChallengeDataManager(this._client)
      const currentChallenge = await cdm.getCurrentChallenge()
      if (!currentChallenge) {
        // No current challenge
        return null
      } else {
        if (
          currentChallenge &&
          currentChallenge.challengeType &&
          currentChallenge.challengeType.type === TypeChallenge.CHALLENGE
        ) {
          // Check if we are in the viewing timezone for current challenge
          const viewingDate = cdm.getViewingDate(currentChallenge, fluidTypes)
          if (DateTime.local() >= viewingDate) {
            if (currentChallenge.maxEnergy === -1) {
              const maxEnergyResult = await cdm.setMaxEnergy(
                currentChallenge,
                this._client,
                fluidTypes
              )
              if (maxEnergyResult > 0) {
                currentChallenge.maxEnergy = maxEnergyResult
              }
            }
            const currentEnergyResult = await cdm.setSpentEnergy(
              currentChallenge,
              this._client,
              fluidTypes
            )
            if (currentEnergyResult) {
              currentChallenge.currentEnergy = currentEnergyResult
            }
          }
        }
        return currentChallenge
      }
    } catch (error) {
      console.log('Context error: ', error)
      throw error
    }
  }

  /*
   * If the challenge is over
   * Set the rigth badge and return true
   * else return false
   */
  public async isCurrentChallengeOver(
    challenge: UserChallenge,
    fluidTypes: FluidType[]
  ): Promise<boolean> {
    try {
      // get the current challenge
      if (!challenge) {
        // No current challenge
        return false
      } else {
        const cdm = new ChallengeDataManager(this._client)
        const typeChallenge = challenge.challengeType
          ? challenge.challengeType.type
          : 0
        const isOver = await cdm.isChallengeOverByDate(
          challenge.endingDate,
          fluidTypes,
          typeChallenge
        )
        if (isOver) {
          await cdm.setTheRightBadge(challenge)
          return true
        } else {
          return false
        }
      }
    } catch (error) {
      console.log('Context error: ', error)
      return false
    }
  }

  public async checkAchievement(id: string): Promise<UserChallenge | null> {
    const cdm = new ChallengeDataManager(this._client)
    return await cdm.checkAchievement(id)
  }
}