import { Model } from 'objection'
import path from 'node:path'
import fs from 'node:fs/promises'
import { capitalize, find, has, hasIn, remove, uniq } from 'lodash-es'
import yaml from 'js-yaml'

/**
 * Storage model
 */
export class Storage extends Model {
  static get tableName() { return 'storage' }
  static get idColumn() { return 'id' }

  static get jsonSchema () {
    return {
      type: 'object',
      required: ['module', 'isEnabled', 'siteId'],

      properties: {
        module: {type: 'string'},
        isEnabled: {type: 'boolean'}
      }
    }
  }

  static get jsonAttributes() {
    return ['contentTypes', 'assetDelivery', 'versioning', 'schedule', 'config', 'state']
  }

  static async getTargets ({ siteId, enabledOnly = false } = {}) {
    return WIKI.db.storage.query().where(builder => {
      if (siteId) {
        builder.where('siteId', siteId)
      }
      if (enabledOnly) {
        builder.where('isEnabled', true)
      }
    })
  }

  static async refreshTargetsFromDisk () {
    let trx
    try {
      // -> Fetch definitions from disk
      const storageDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/storage'))
      WIKI.storage.defs = []
      for (const dir of storageDirs) {
        const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/storage', dir, 'definition.yml'), 'utf8')
        const defParsed = yaml.load(def)
        defParsed.key = dir
        defParsed.isLoaded = false
        WIKI.storage.defs.push(defParsed)
        WIKI.logger.debug(`Loaded storage module definition ${dir}: [ OK ]`)
      }
      WIKI.logger.info(`Loaded ${WIKI.storage.defs.length} storage module definitions: [ OK ]`)
    } catch (err) {
      WIKI.logger.error('Failed to scan or load new storage providers: [ FAILED ]')
      WIKI.logger.error(err)
      if (trx) {
        trx.rollback()
      }
    }
  }

  /**
 * Ensure a storage module is loaded
 */
  static async ensureModule (moduleName) {
    if (!has(WIKI.storage.modules, moduleName)) {
      try {
        WIKI.storage.modules[moduleName] = (await import(`../modules/storage/${moduleName}/storage.mjs`)).default
        WIKI.logger.debug(`Activated storage module ${moduleName}: [ OK ]`)
        return true
      } catch (err) {
        WIKI.logger.warn(`Failed to load storage module ${moduleName}: [ FAILED ]`)
        WIKI.logger.warn(err)
        return false
      }
    } else {
      return true
    }
  }

  /**
   * Initialize active storage targets
   */
  static async initTargets () {
    const dbTargets = await WIKI.db.storage.query().where('isEnabled', true)
    const activeModules = uniq(dbTargets.map(t => t.module))
    try {
      // -> Stop and delete existing jobs
      // const prevjobs = remove(WIKI.scheduler.jobs, job => job.name === 'sync-storage')
      // if (prevjobs.length > 0) {
      //   prevjobs.forEach(job => job.stop())
      // }

      // -> Load active modules
      for (const md of activeModules) {
        this.ensureModule(md)
      }

      // -> Initialize targets
      // for (const target of this.targets) {
      //   const targetDef = find(WIKI.data.storage, ['key', target.key])
      //   target.fn = require(`../modules/storage/${target.key}/storage`)
      //   target.fn.config = target.config
      //   target.fn.mode = target.mode
      //   try {
      //     await target.fn.init()

      //     // -> Save succeeded init state
      //     await WIKI.db.storage.query().patch({
      //       state: {
      //         status: 'operational',
      //         message: '',
      //         lastAttempt: new Date().toISOString()
      //       }
      //     }).where('key', target.key)

      //     // -> Set recurring sync job
      //     if (targetDef.schedule && target.syncInterval !== 'P0D') {
      //       WIKI.scheduler.registerJob({
      //         name: 'sync-storage',
      //         immediate: false,
      //         schedule: target.syncInterval,
      //         repeat: true
      //       }, target.key)
      //     }

      //     // -> Set internal recurring sync job
      //     if (targetDef.internalSchedule && targetDef.internalSchedule !== 'P0D') {
      //       WIKI.scheduler.registerJob({
      //         name: 'sync-storage',
      //         immediate: false,
      //         schedule: target.internalSchedule,
      //         repeat: true
      //       }, target.key)
      //     }
      //   } catch (err) {
      //     // -> Save initialization error
      //     await WIKI.db.storage.query().patch({
      //       state: {
      //         status: 'error',
      //         message: err.message,
      //         lastAttempt: new Date().toISOString()
      //       }
      //     }).where('key', target.key)
      //   }
      // }
    } catch (err) {
      WIKI.logger.warn(err)
      throw err
    }
  }

  static async pageEvent({ event, page }) {
    try {
      for (let target of this.targets) {
        await target.fn[event](page)
      }
    } catch (err) {
      WIKI.logger.warn(err)
      throw err
    }
  }

  static async assetEvent({ event, asset }) {
    try {
      for (let target of this.targets) {
        await target.fn[`asset${capitalize(event)}`](asset)
      }
    } catch (err) {
      WIKI.logger.warn(err)
      throw err
    }
  }

  static async getLocalLocations({ asset }) {
    const locations = []
    const promises = this.targets.map(async (target) => {
      try {
        const path = await target.fn.getLocalLocation(asset)
        locations.push({
          path,
          key: target.key
        })
      } catch (err) {
        WIKI.logger.warn(err)
      }
    })
    await Promise.all(promises)
    return locations
  }

  static async executeAction(targetKey, handler) {
    try {
      const target = find(this.targets, ['key', targetKey])
      if (target) {
        if (hasIn(target.fn, handler)) {
          await target.fn[handler]()
        } else {
          throw new Error('Invalid Handler for Storage Target')
        }
      } else {
        throw new Error('Invalid or Inactive Storage Target')
      }
    } catch (err) {
      WIKI.logger.warn(err)
      throw err
    }
  }
}
