import { getRepository, EntityManager, In } from 'typeorm'

import { Sequence, SEQUENCE_TYPE } from '@things-factory/id-rule-base'
import { buildQuery, Domain, ListParam, convertListParams } from '@things-factory/shell'
import { User } from '@things-factory/auth-base'
import {
  getMyBizplace,
  getPartnerBizplaceIds,
  getPartnersBizplaces,
  getPartnersCompanyBizplaces,
  getPermittedBizplaceIds,
  Bizplace
} from '@things-factory/biz-base'
import { Setting, PartnerSetting } from '@things-factory/setting-base'

import { CommonCode as CommonCodeEntity, CommonCodeDetail as CommonCodeDetailEntity } from '@things-factory/code-base'

export const pageSettings = {
  async pageSettings(_: any, { codes, partners, settings, settingsDomain }, context: any) {
    const { domain, user, tx }: { domain: Domain; user: User; tx: EntityManager } = context.state
    try {
      let promiseTask = []
      if (codes) {
        promiseTask.push(queryCommonCode(codes, domain, tx, context))
      }

      if (partners) {
        promiseTask.push(queryPartners(partners, domain, tx, context))
      }

      if (settings) {
        promiseTask.push(querySettings(settings, settingsDomain, domain, tx))
      }

      return await Promise.all(promiseTask)
    } catch (error) {
      throw error
    }
  }
}

async function queryCommonCode(codes, domain, tx, context) {
  const codeQb = tx.getRepository(CommonCodeEntity).createQueryBuilder('cc')
  buildQuery(codeQb, { filters: codes }, context)

  codes = (
    await codeQb
      .leftJoinAndSelect('cc.details', 'ccdet')
      .orderBy('cc.name')
      .addOrderBy('ccdet.rank')
      .addOrderBy('ccdet.name')
      .getMany()
  ).map((cc: CommonCodeEntity) => {
    return {
      id: cc.id,
      name: cc.name,
      description: cc.description,
      value: null,
      settingDetails: cc.details.map(det => {
        return {
          id: det.id,
          name: det.name,
          description: det.description,
          rank: det.rank
        }
      })
    }
  })

  return {
    type: 'code',
    details: codes
  }
}

async function queryPartners(partners, domain, tx, context) {
  let partnersBizplaceIds = []
  let bizplaceIndex: any = partners.filters?.findIndex(x => x.name === 'bizplace')
  if (bizplaceIndex && bizplaceIndex > -1) {
    partnersBizplaceIds = partners.filters
      .filter(x => x.name === 'bizplace')
      .map(item => {
        return item.value
      })
    partners.filters.splice(bizplaceIndex, 1)
  }

  const convertedParams = convertListParams({ filters: partners })

  if (!(partnersBizplaceIds.length > 0)) {
    const partnersBizplaces: Bizplace[] = await getPartnersBizplaces(context.state.domain, context.state.user)
    partnersBizplaceIds = partnersBizplaces.map((bizplace: Bizplace) => bizplace.id || null)
    convertedParams.where.id = In(partnersBizplaceIds)
  } else {
    convertedParams.where.id = In(partnersBizplaceIds)
  }

  partners = (
    await tx.getRepository(Bizplace).find({
      ...convertedParams,
      relations: ['domain', 'company', 'users', 'creator', 'updater']
    })
  ).map((biz: Bizplace) => {
    return {
      id: biz.id,
      name: biz.name,
      description: biz.description,
      value: null
    }
  })

  return {
    type: 'partner',
    details: partners
  }
}

async function querySettings(settingsFilter, partnerDomainId, domain, tx) {
  let settingsName = settingsFilter.map(setting => setting.value).flat()

  let domainSettings = (
    await tx
      .getRepository(Setting)
      .createQueryBuilder('setting')
      .where('setting.name IN (:...names)', { names: settingsName })
      .andWhere('setting.domain = :domainId', { domainId: domain.id })
      .getMany()
  ).map((setting: Setting) => {
    return {
      id: setting.id,
      name: setting.name,
      description: setting.description,
      value: setting.value
    }
  })

  let partnerSettings = []
  if (partnerDomainId) {
    partnerSettings = (
      await tx
        .getRepository(PartnerSetting)
        .createQueryBuilder('ps')
        .leftJoinAndSelect('ps.setting', 'setting')
        .where('ps.setting IN (:...settings)', { settings: domainSettings.map(setting => setting.id) })
        .andWhere('ps.domain = :domainId', { domainId: domain.id })
        .andWhere('ps.partnerDomain = :partnerDomainId', { partnerDomainId })
        .getMany()
    ).map((ps: PartnerSetting) => {
      return {
        id: ps.setting.id,
        name: ps.setting.name,
        description: ps.setting.description,
        value: ps.value
      }
    })
  }

  //filter out domain settings that exist in partner settings
  const partnerSettingIds = new Set(partnerSettings.map(ps => ps.id))
  const filteredDomainSettings = domainSettings.filter(ds => !partnerSettingIds.has(ds.id))

  let settings = [...filteredDomainSettings, ...partnerSettings]

  return {
    type: 'setting',
    details: settings
  }
}
