import type {
  DataFilter,
  EndpointMeta,
  EndpointResponse,
} from '@factor/api'
import type { FactorAdminSettings } from '@factor/plugin-admin'
import { AdminQuery } from '@factor/plugin-admin'
import type { TableCardConfig, TableSiteConfig } from './tables'
import { tableNames } from './tables'
import { themes } from './themes'
import type { FactorSites } from '.'

type SitesQuerySettings = FactorAdminSettings & {
  factorSites: FactorSites
}
export abstract class SitesQuery extends AdminQuery<SitesQuerySettings> {
  constructor(settings: SitesQuerySettings) {
    super(settings)
  }
}

type ManageRegionParams = {
  userId: string
  organizationId: string
  successMessage?: string
} & ({ _action: 'upsert', fields: Partial<TableCardConfig> & { siteId: string } } | { _action: 'retrieve' | 'delete', fields: Partial<TableCardConfig> & { siteId: string } })

export class ManageRegion extends SitesQuery {
  async run(
    params: ManageRegionParams,
    meta: EndpointMeta,
  ): Promise<EndpointResponse<TableCardConfig>> {
    const factorDb = this.settings.factorDb
    if (!factorDb)
      throw this.stop('no factorDb')

    const { _action, fields, organizationId, userId, successMessage = '' } = params

    const db = factorDb.client()

    const message = successMessage
    let data: TableCardConfig | undefined

    if (_action === 'upsert') {
      const { siteId } = fields

      if (!siteId)
        throw this.stop('siteId field required')

      const defaults = {
        regionKey: 'page',
        cards: [],
        userConfig: {},
      }

      const prepped = this.utils.prepareFields({
        type: 'create',
        fields: { ...defaults, ...fields },
        table: tableNames.regions,
        meta,
        factorDb,
      })

      // Create the site
      const [region] = await db
        .insert({ organizationId, userId, ...prepped, siteId })
        .into(tableNames.regions)
        .onConflict('card_id')
        .merge()
        .returning<TableCardConfig[]>('*')

      data = region
    }
    else if (_action === 'retrieve') {
      const { cardId } = fields

      if (!cardId)
        throw this.stop('cardId required')

      data = await db
        .select()
        .from(tableNames.regions)
        .where({ organizationId, cardId })
        .first()

      if (!data)
        throw this.stop('Region not found')
    }
    else if (_action === 'delete') {
      const { cardId } = fields

      if (!cardId)
        throw this.stop('cardId required')

      await db
        .delete()
        .from(tableNames.regions)
        .where({ organizationId, cardId })

      data = undefined
    }

    return { status: 'success', data, message }
  }
}

type ManageSiteParams = {
  fields: Partial<TableSiteConfig>
  userId: string
  organizationId: string
} & ({ _action: 'create', fields: { themeId: string } } | { _action: 'retrieve' | 'update' | 'delete', fields: { siteId: string } })

export class ManageSite extends SitesQuery {
  async run(
    params: ManageSiteParams,
    meta: EndpointMeta,
  ): Promise<EndpointResponse<TableSiteConfig>> {
    const factorDb = this.settings.factorDb
    if (!factorDb)
      throw this.stop('no factorDb')

    const { _action, fields, organizationId, userId } = params

    const db = factorDb.client()

    let message = ''
    let data: TableSiteConfig | undefined
    let siteId: string | undefined
    if (_action === 'create') {
      const { getRandomWordsId } = await import('@factor/api/utils/words')

      const { themeId } = fields
      if (!themeId)
        throw this.stop('theme required')

      const theme = themes.find(t => t.themeId === themeId)

      if (!theme)
        throw this.stop(`theme not found (${themeId})`)

      const defaults = {
        slug: getRandomWordsId(),
      }

      const fullConfig = {
        ...defaults,
        ...theme.toConfig(),
        ...fields,
      }

      this.log.info(`creating site: ${fullConfig.title}`, { data: { userId, organizationId, fullConfig } })

      const prepped = this.utils.prepareFields({
        type: 'settings',
        fields: fullConfig,
        table: tableNames.sites,
        meta,
        factorDb,
      })

      // Create the site
      const [site] = await db
        .insert({ organizationId, userId, ...prepped })
        .into(tableNames.sites)
        .returning<TableSiteConfig[]>('*')

      if (!site?.siteId)
        throw this.stop('site not created')

      siteId = site.siteId

      const regionConfigPrepped = (fullConfig.regions || []).map((region) => {
        return { siteId, userId, organizationId, ...this.utils.prepareFields({
          type: 'settings',
          fields: region,
          table: tableNames.regions,
          meta,
          factorDb,
        }) }
      })

      // Create the pages
      await db
        .insert(regionConfigPrepped)
        .into(tableNames.regions)
        .returning<TableSiteConfig[]>('*')

      message = 'site created'

      await this.settings.factorMonitor?.slackNotify({ message: '*New Site Created*', data })
    }
    else if (_action === 'retrieve') {
      const { siteId } = fields

      if (!siteId)
        throw this.stop('siteId required')

      data = await db
        .select()
        .from(tableNames.sites)
        .where({ organizationId, siteId })
        .first()

      if (!data)
        throw this.stop('Site not found')

      // Retrieve all pages associated with the siteId
      const pages = await db
        .select()
        .from(tableNames.regions)
        .where({ siteId })

      // Assign the pages to the site object
      data.regions = pages
    }
    else if (_action === 'update') {
      siteId = fields.siteId

      if (!siteId)
        throw this.stop('siteId required')

      const prepped = this.utils.prepareFields({
        type: 'settings',
        fields,
        table: tableNames.sites,
        meta,
        factorDb,
      })

      ;[data] = await db
        .update({ organizationId, userId, siteId, ...prepped })
        .where({ organizationId, siteId })
        .into(tableNames.sites)
        .returning<TableSiteConfig[]>('*')

      if (fields.regions && fields.regions.length) {
        const upsertPromises = fields.regions.map(async (page) => {
          if (!siteId)
            return

          return await this.settings.factorSites.queries.ManageRegion.serve({ _action: 'upsert', fields: { siteId, ...page }, userId, organizationId }, meta)
        })

        await Promise.all(upsertPromises)
      }

      message = 'site saved'
    }

    /**
     * Return full site via action when creating or updating
     */

    if ((_action === 'update' || _action === 'create') && siteId) {
      const r = await this.run({ _action: 'retrieve', fields: { siteId }, userId, organizationId }, { ...meta, caller: 'endManageSite' })

      data = r.data
    }

    return { status: 'success', data, message }
  }
}

interface ManageIndexParams {
  _action: 'delete' | 'list'
  userId?: string
  organizationId: string
  limit?: number
  offset?: number
  selectedIds?: string[]
  filters?: DataFilter[]
}
export class ManageIndex extends SitesQuery {
  async run(
    params: ManageIndexParams,
    _meta: EndpointMeta,
  ): Promise<EndpointResponse<TableSiteConfig[]>> {
    const {
      _action,
      organizationId,
      limit = 40,
      offset = 0,
      filters = [],
    } = params

    if (!_action)
      throw this.stop('action required')

    const db = this.settings.factorDb.client()

    const message: string | undefined = undefined
    let data: TableSiteConfig[] | undefined

    let count = 0
    if (_action === 'list') {
      const base = db
        .select([
          `${tableNames.sites}.*`,
        ])
        .from(tableNames.sites)
        .where(`${tableNames.sites}.organization_id`, organizationId)
        .limit(limit)
        .offset(offset)
        .orderBy('updatedAt', 'desc')
        .groupBy(`${tableNames.sites}.site_id`)

      if (filters.length) {
        filters.forEach((filter) => {
          const { field, operator, value } = filter
          if (field && operator && value)
            void base.where(field, operator, value)
        })
      }

      const rows = await base

      data = rows

      const r = await db
        .count<{ count: string }>('*')
        .from(tableNames.sites)
        .where({ organizationId })
        .first()

      count = +(r?.count || 0)
    }

    this.log.info('running query', { data: { params, data, count } })

    return {
      status: 'success',
      data,
      message,
      indexMeta: { count, limit, offset },
      params,
    }
  }
}
