import { generateError, generateSuccess } from '../../helpers/graph.mjs'
import _ from 'lodash-es'
import CleanCSS from 'clean-css'
import path from 'node:path'
import fs from 'fs-extra'
import { v4 as uuid } from 'uuid'

export default {
  Query: {
    async sites () {
      const sites = await WIKI.db.sites.query().orderBy('hostname')
      return sites.map(s => ({
        ...s.config,
        id: s.id,
        hostname: s.hostname,
        isEnabled: s.isEnabled,
        pageExtensions: s.config.pageExtensions.join(', ')
      }))
    },
    async siteById (obj, args) {
      const site = await WIKI.db.sites.query().findById(args.id)
      return site ? {
        ...site.config,
        id: site.id,
        hostname: site.hostname,
        isEnabled: site.isEnabled,
        pageExtensions: site.config.pageExtensions.join(', ')
      } : null
    },
    async siteByHostname (obj, args) {
      let site = await WIKI.db.sites.query().where({
        hostname: args.hostname
      }).first()
      if (!site && !args.exact) {
        site = await WIKI.db.sites.query().where({
          hostname: '*'
        }).first()
      }
      return site ? {
        ...site.config,
        id: site.id,
        hostname: site.hostname,
        isEnabled: site.isEnabled,
        pageExtensions: site.config.pageExtensions.join(', ')
      } : null
    }
  },
  Mutation: {
    /**
     * CREATE SITE
     */
    async createSite (obj, args) {
      try {
        // -> Validate inputs
        if (!args.hostname || args.hostname.length < 1 || !/^(\\*)|([a-z0-9\-.:]+)$/.test(args.hostname)) {
          throw new WIKI.Error.Custom('SiteCreateInvalidHostname', 'Invalid Site Hostname')
        }
        if (!args.title || args.title.length < 1 || !/^[^<>"]+$/.test(args.title)) {
          throw new WIKI.Error.Custom('SiteCreateInvalidTitle', 'Invalid Site Title')
        }
        // -> Check for duplicate catch-all
        if (args.hostname === '*') {
          const site = await WIKI.db.sites.query().where({
            hostname: args.hostname
          }).first()
          if (site) {
            throw new WIKI.Error.Custom('SiteCreateDuplicateCatchAll', 'A site with a catch-all hostname already exists! Cannot have 2 catch-all hostnames.')
          }
        }
        // -> Create site
        const newSite = await WIKI.db.sites.createSite(args.hostname, {
          title: args.title
        })
        return {
          operation: generateSuccess('Site created successfully'),
          site: newSite
        }
      } catch (err) {
        WIKI.logger.warn(err)
        return generateError(err)
      }
    },
    /**
     * UPDATE SITE
     */
    async updateSite (obj, args) {
      try {
        // -> Load site
        const site = await WIKI.db.sites.query().findById(args.id)
        if (!site) {
          throw new WIKI.Error.Custom('SiteInvalidId', 'Invalid Site ID')
        }
        // -> Check for bad input
        if (_.has(args.patch, 'hostname') && _.trim(args.patch.hostname).length < 1) {
          throw new WIKI.Error.Custom('SiteInvalidHostname', 'Hostname is invalid.')
        }
        // -> Check for duplicate catch-all
        if (args.patch.hostname === '*' && site.hostname !== '*') {
          const dupSite = await WIKI.db.sites.query().where({ hostname: '*' }).first()
          if (dupSite) {
            throw new WIKI.Error.Custom('SiteUpdateDuplicateCatchAll', `Site ${dupSite.config.title} with a catch-all hostname already exists! Cannot have 2 catch-all hostnames.`)
          }
        }
        // -> Format Code
        if (args.patch?.theme?.injectCSS) {
          args.patch.theme.injectCSS = new CleanCSS({ inline: false }).minify(args.patch.theme.injectCSS).styles
        }
        // -> Format Page Extensions
        if (args.patch?.pageExtensions) {
          args.patch.pageExtensions = args.patch.pageExtensions.split(',').map(ext => ext.trim().toLowerCase()).filter(ext => ext.length > 0)
        }
        // -> Update site
        await WIKI.db.sites.updateSite(args.id, {
          hostname: args.patch.hostname ?? site.hostname,
          isEnabled: args.patch.isEnabled ?? site.isEnabled,
          config: _.defaultsDeep(_.omit(args.patch, ['hostname', 'isEnabled']), site.config)
        })

        return {
          operation: generateSuccess('Site updated successfully')
        }
      } catch (err) {
        WIKI.logger.warn(err)
        return generateError(err)
      }
    },
    /**
     * DELETE SITE
     */
    async deleteSite (obj, args) {
      try {
        // -> Ensure site isn't last one
        const sitesCount = await WIKI.db.sites.query().count('id').first()
        if (sitesCount?.count && _.toNumber(sitesCount?.count) <= 1) {
          throw new WIKI.Error.Custom('SiteDeleteLastSite', 'Cannot delete the last site. At least 1 site must exists at all times.')
        }
        // -> Delete site
        await WIKI.db.sites.deleteSite(args.id)
        return {
          operation: generateSuccess('Site deleted successfully')
        }
      } catch (err) {
        WIKI.logger.warn(err)
        return generateError(err)
      }
    },
    /**
     * UPLOAD LOGO
     */
    async uploadSiteLogo (obj, args, context) {
      try {
        const { filename, mimetype, createReadStream } = await args.image
        WIKI.logger.info(`Processing site logo ${filename} of type ${mimetype}...`)
        if (!WIKI.extensions.ext.sharp.isInstalled) {
          throw new Error('This feature requires the Sharp extension but it is not installed.')
        }
        if (!['.svg', '.png', '.jpg', 'webp', '.gif'].some(s => filename.endsWith(s))) {
          throw new Error('Invalid File Extension. Must be svg, png, jpg, webp or gif.')
        }
        const destFormat = mimetype.startsWith('image/svg') ? 'svg' : 'png'
        const destFolder = path.resolve(
          process.cwd(),
          WIKI.config.dataPath,
          `assets`
        )
        const destPath = path.join(destFolder, `logo-${args.id}.${destFormat}`)
        await fs.ensureDir(destFolder)
        // -> Resize
        await WIKI.extensions.ext.sharp.resize({
          format: destFormat,
          inputStream: createReadStream(),
          outputPath: destPath,
          height: 72
        })
        // -> Save logo meta to DB
        const site = await WIKI.db.sites.query().findById(args.id)
        if (!site.config.assets.logo) {
          site.config.assets.logo = uuid()
        }
        site.config.assets.logoExt = destFormat
        await WIKI.db.sites.query().findById(args.id).patch({ config: site.config })
        await WIKI.db.sites.reloadCache()
        // -> Save image data to DB
        const imgBuffer = await fs.readFile(destPath)
        await WIKI.db.knex('assets').insert({
          id: site.config.assets.logo,
          fileName: `_logo.${destFormat}`,
          fileExt: `.${destFormat}`,
          isSystem: true,
          kind: 'image',
          mimeType: (destFormat === 'svg') ? 'image/svg' : 'image/png',
          fileSize: Math.ceil(imgBuffer.byteLength / 1024),
          data: imgBuffer,
          authorId: context.req.user.id,
          siteId: site.id
        }).onConflict('id').merge()
        WIKI.logger.info('New site logo processed successfully.')
        return {
          operation: generateSuccess('Site logo uploaded successfully')
        }
      } catch (err) {
        WIKI.logger.warn(err)
        return generateError(err)
      }
    },
    /**
     * UPLOAD FAVICON
     */
    async uploadSiteFavicon (obj, args, context) {
      try {
        const { filename, mimetype, createReadStream } = await args.image
        WIKI.logger.info(`Processing site favicon ${filename} of type ${mimetype}...`)
        if (!WIKI.extensions.ext.sharp.isInstalled) {
          throw new Error('This feature requires the Sharp extension but it is not installed.')
        }
        if (!['.svg', '.png', '.jpg', '.webp', '.gif'].some(s => filename.endsWith(s))) {
          throw new Error('Invalid File Extension. Must be svg, png, jpg, webp or gif.')
        }
        const destFormat = mimetype.startsWith('image/svg') ? 'svg' : 'png'
        const destFolder = path.resolve(
          process.cwd(),
          WIKI.config.dataPath,
          `assets`
        )
        const destPath = path.join(destFolder, `favicon-${args.id}.${destFormat}`)
        await fs.ensureDir(destFolder)
        // -> Resize
        await WIKI.extensions.ext.sharp.resize({
          format: destFormat,
          inputStream: createReadStream(),
          outputPath: destPath,
          width: 64,
          height: 64
        })
        // -> Save favicon meta to DB
        const site = await WIKI.db.sites.query().findById(args.id)
        if (!site.config.assets.favicon) {
          site.config.assets.favicon = uuid()
        }
        site.config.assets.faviconExt = destFormat
        await WIKI.db.sites.query().findById(args.id).patch({ config: site.config })
        await WIKI.db.sites.reloadCache()
        // -> Save image data to DB
        const imgBuffer = await fs.readFile(destPath)
        await WIKI.db.knex('assets').insert({
          id: site.config.assets.favicon,
          fileName: `_favicon.${destFormat}`,
          fileExt: `.${destFormat}`,
          isSystem: true,
          kind: 'image',
          mimeType: (destFormat === 'svg') ? 'image/svg' : 'image/png',
          fileSize: Math.ceil(imgBuffer.byteLength / 1024),
          data: imgBuffer,
          authorId: context.req.user.id,
          siteId: site.id
        }).onConflict('id').merge()
        WIKI.logger.info('New site favicon processed successfully.')
        return {
          operation: generateSuccess('Site favicon uploaded successfully')
        }
      } catch (err) {
        WIKI.logger.warn(err)
        return generateError(err)
      }
    },
    /**
     * UPLOAD LOGIN BG
     */
    async uploadSiteLoginBg (obj, args, context) {
      try {
        const { filename, mimetype, createReadStream } = await args.image
        WIKI.logger.info(`Processing site login bg ${filename} of type ${mimetype}...`)
        if (!WIKI.extensions.ext.sharp.isInstalled) {
          throw new Error('This feature requires the Sharp extension but it is not installed.')
        }
        if (!['.png', '.jpg', '.webp'].some(s => filename.endsWith(s))) {
          throw new Error('Invalid File Extension. Must be png, jpg or webp.')
        }
        const destFolder = path.resolve(
          process.cwd(),
          WIKI.config.dataPath,
          `assets`
        )
        const destPath = path.join(destFolder, `loginbg-${args.id}.jpg`)
        await fs.ensureDir(destFolder)
        // -> Resize
        await WIKI.extensions.ext.sharp.resize({
          format: 'jpg',
          inputStream: createReadStream(),
          outputPath: destPath,
          width: 1920
        })
        // -> Save login bg meta to DB
        const site = await WIKI.db.sites.query().findById(args.id)
        if (!site.config.assets.loginBg) {
          site.config.assets.loginBg = uuid()
          await WIKI.db.sites.query().findById(args.id).patch({ config: site.config })
          await WIKI.db.sites.reloadCache()
        }
        // -> Save image data to DB
        const imgBuffer = await fs.readFile(destPath)
        await WIKI.db.knex('assets').insert({
          id: site.config.assets.loginBg,
          filename: '_loginbg.jpg',
          hash: '_loginbg',
          ext: '.jpg',
          isSystem: true,
          kind: 'image',
          mime: 'image/jpg',
          fileSize: Math.ceil(imgBuffer.byteLength / 1024),
          data: imgBuffer,
          authorId: context.req.user.id,
          siteId: site.id
        }).onConflict('id').merge()
        WIKI.logger.info('New site login bg processed successfully.')
        return {
          operation: generateSuccess('Site login bg uploaded successfully')
        }
      } catch (err) {
        WIKI.logger.warn(err)
        return generateError(err)
      }
    }
  },
  SiteLocales: {
    async active (obj, args, context) {
      return obj.active.map(l => WIKI.cache.get(`locale:${l}`))
    }
  }
}
