/**
 * Example: Operato WMS Setting Validators
 *
 * This file demonstrates how to register application-specific validators
 * for settings used in operato-wms.
 */

import { settingValidatorRegistry, SettingValidationResult, SettingBatchValidator } from '@things-factory/setting-base'
import { Domain } from '@things-factory/shell'
import { User } from '@things-factory/auth-base'
import { NewSetting, SettingPatch } from '@things-factory/setting-base'
import { Setting } from '@things-factory/setting-base'
import { getRepository } from 'typeorm'

/**
 * Validate batch-picking-limit setting
 * Ensures the value is a positive number between 1 and 1000
 */
async function validateBatchPickingLimit(
  setting: NewSetting,
  context: { domain: Domain; user: User }
): Promise<SettingValidationResult> {
  if (!setting.value) {
    return { valid: false, error: 'batch-picking-limit value is required' }
  }

  const limit = parseInt(setting.value, 10)
  if (isNaN(limit) || limit < 1 || limit > 1000) {
    return {
      valid: false,
      error: 'batch-picking-limit must be a number between 1 and 1000'
    }
  }

  return { valid: true }
}

/**
 * Validate stacking-option-limit setting
 * Ensures the value is a valid JSON with Min and Max properties
 */
async function validateStackingOptionLimit(
  setting: NewSetting,
  context: { domain: Domain; user: User }
): Promise<SettingValidationResult> {
  if (!setting.value) {
    return { valid: false, error: 'stacking-option-limit value is required' }
  }

  try {
    const parsed = JSON.parse(setting.value)
    if (typeof parsed.Min !== 'number' || typeof parsed.Max !== 'number') {
      return {
        valid: false,
        error: 'stacking-option-limit must have Min and Max as numbers'
      }
    }
    if (parsed.Min < 0 || parsed.Max < parsed.Min) {
      return {
        valid: false,
        error: 'stacking-option-limit: Min must be >= 0 and Max must be >= Min'
      }
    }
  } catch (e) {
    return {
      valid: false,
      error: 'stacking-option-limit must be valid JSON format: { "Min": 1, "Max": 1 }'
    }
  }

  return { valid: true }
}

async function validateStackingOptionLimitUpdate(
  existingSetting: Setting,
  patch: SettingPatch,
  context: { domain: Domain; user: User }
): Promise<SettingValidationResult> {
  if (patch.value === undefined) {
    return { valid: true }
  }

  return await validateStackingOptionLimit({ ...existingSetting, value: patch.value } as NewSetting, context)
}

/**
 * Validate location sorting rule settings
 * Ensures the value is valid JSON array with name and desc properties
 */
async function validateLocationSortingRule(
  setting: NewSetting,
  context: { domain: Domain; user: User }
): Promise<SettingValidationResult> {
  if (!setting.value) {
    return { valid: true } // Allow empty value
  }

  try {
    const rules = JSON.parse(setting.value)
    if (!Array.isArray(rules)) {
      return {
        valid: false,
        error: 'Location sorting rule must be a JSON array'
      }
    }

    for (const rule of rules) {
      if (typeof rule.name !== 'string') {
        return {
          valid: false,
          error: 'Each rule must have a "name" property as string'
        }
      }
      if (typeof rule.desc !== 'boolean') {
        return {
          valid: false,
          error: 'Each rule must have a "desc" property as boolean'
        }
      }
    }
  } catch (e) {
    return {
      valid: false,
      error: 'Location sorting rule must be valid JSON array format'
    }
  }

  return { valid: true }
}

/**
 * Validate boolean settings (enable/disable flags)
 */
async function validateBooleanSetting(
  setting: NewSetting,
  context: { domain: Domain; user: User }
): Promise<SettingValidationResult> {
  if (setting.value && setting.value !== 'true' && setting.value !== 'false') {
    return {
      valid: false,
      error: `${setting.name} must be "true" or "false"`
    }
  }

  return { valid: true }
}

/**
 * Validate update for batch-picking-limit
 */
async function validateBatchPickingLimitUpdate(
  existingSetting: Setting,
  patch: SettingPatch,
  context: { domain: Domain; user: User }
): Promise<SettingValidationResult> {
  // Only validate if value is being updated
  if (patch.value === undefined) {
    return { valid: true }
  }

  return await validateBatchPickingLimit({ ...existingSetting, value: patch.value } as NewSetting, context)
}

/**
 * Validate minimum-seal-number setting
 * Ensures the value is a non-negative integer (0 or above, no decimals)
 */
async function validateMinimumSealNumber(
  setting: NewSetting,
  context: { domain: Domain; user: User }
): Promise<SettingValidationResult> {
  if (!setting.value) {
    return { valid: false, error: 'minimum-seal-number value is required' }
  }

  // Check if value contains decimal point
  if (setting.value.includes('.')) {
    return {
      valid: false,
      error: 'minimum-seal-number must be an integer (no decimals allowed)'
    }
  }

  const number = parseInt(setting.value, 10)

  // Check if parsing resulted in NaN
  if (isNaN(number)) {
    return {
      valid: false,
      error: 'minimum-seal-number must be a valid number'
    }
  }

  // Check if the parsed value matches the original string (to catch cases like "123abc")
  if (number.toString() !== setting.value.trim()) {
    return {
      valid: false,
      error: 'minimum-seal-number must be a valid integer'
    }
  }

  // Check if negative
  if (number < 0) {
    return {
      valid: false,
      error: 'minimum-seal-number must be 0 or above (negative numbers not allowed)'
    }
  }

  return { valid: true }
}

/**
 * Validate update for minimum-seal-number
 */
async function validateMinimumSealNumberUpdate(
  existingSetting: Setting,
  patch: SettingPatch,
  context: { domain: Domain; user: User }
): Promise<SettingValidationResult> {
  // Only validate if value is being updated
  if (patch.value === undefined) {
    return { valid: true }
  }

  return await validateMinimumSealNumber({ ...existingSetting, value: patch.value } as NewSetting, context)
}

/**
 * Validate enable-tote-scanning setting
 * Ensures the value is a non-negative integer (0 or above, no decimals)
 * 0 = disabled, >= 1 = enabled
 */
async function validateEnableToteScanning(
  setting: NewSetting,
  context: { domain: Domain; user: User }
): Promise<SettingValidationResult> {
  if (!setting.value) {
    return { valid: false, error: 'enable-tote-scanning value is required' }
  }

  // Check if value contains decimal point
  if (setting.value.includes('.')) {
    return {
      valid: false,
      error: 'enable-tote-scanning must be an integer (no decimals allowed)'
    }
  }

  const number = parseInt(setting.value, 10)

  // Check if parsing resulted in NaN
  if (isNaN(number)) {
    return {
      valid: false,
      error: 'enable-tote-scanning must be a valid number'
    }
  }

  // Check if the parsed value matches the original string (to catch cases like "123abc")
  if (number.toString() !== setting.value.trim()) {
    return {
      valid: false,
      error: 'enable-tote-scanning must be a valid integer'
    }
  }

  // Check if negative
  if (number < 0) {
    return {
      valid: false,
      error: 'enable-tote-scanning must be 0 or above (negative numbers not allowed)'
    }
  }

  // Cross-validation: If enable-tote-scanning >= 1, qc-sku must be false
  if (number >= 1) {
    const qcSkuSetting: Setting = await getRepository(Setting).findOne({
      where: { domain: context.domain, name: 'qc-sku' }
    })

    if (qcSkuSetting && (qcSkuSetting.value === 'true' || qcSkuSetting.value === true)) {
      return {
        valid: false,
        error: 'enable-tote-scanning cannot be set to 1 or above when qc-sku is enabled. Please disable qc-sku first.'
      }
    }
  }

  // Cross-validation: If enable-tote-scanning >= 2, disable-qc-in-loading must be false
  if (number >= 2) {
    const disableQcInLoadingSetting: Setting = await getRepository(Setting).findOne({
      where: { domain: context.domain, name: 'disable-QC-in-loading' }
    })

    if (
      disableQcInLoadingSetting &&
      (disableQcInLoadingSetting.value === 'true' || disableQcInLoadingSetting.value === true)
    ) {
      return {
        valid: false,
        error:
          'enable-tote-scanning cannot be set to 2 or above when disable-qc-in-loading is true. Please set disable-qc-in-loading to false first.'
      }
    }
  }

  return { valid: true }
}

/**
 * Validate update for enable-tote-scanning
 */
async function validateEnableToteScanningUpdate(
  existingSetting: Setting,
  patch: SettingPatch,
  context: { domain: Domain; user: User }
): Promise<SettingValidationResult> {
  // Only validate if value is being updated
  if (patch.value === undefined) {
    return { valid: true }
  }

  const newValue = parseInt(patch.value || '0', 10)

  // Cross-validation: If enable-tote-scanning is being set to >= 1, qc-sku must be false
  if (newValue >= 1) {
    const qcSkuSetting: Setting = await getRepository(Setting).findOne({
      where: { domain: context.domain, name: 'qc-sku' }
    })

    if (qcSkuSetting && (qcSkuSetting.value === 'true' || qcSkuSetting.value === true)) {
      return {
        valid: false,
        error: 'enable-tote-scanning cannot be set to 1 or above when qc-sku is enabled. Please disable qc-sku first.'
      }
    }
  }

  // Cross-validation: If enable-tote-scanning is being set to >= 2, disable-qc-in-loading must be false
  if (newValue >= 2) {
    const disableQcInLoadingSetting: Setting = await getRepository(Setting).findOne({
      where: { domain: context.domain, name: 'disable-QC-in-loading' }
    })

    if (
      disableQcInLoadingSetting &&
      (disableQcInLoadingSetting.value === 'true' || disableQcInLoadingSetting.value === true)
    ) {
      return {
        valid: false,
        error:
          'enable-tote-scanning cannot be set to 2 or above when disable-qc-in-loading is true. Please set disable-qc-in-loading to false first.'
      }
    }
  }

  return await validateEnableToteScanning({ ...existingSetting, value: patch.value } as NewSetting, context)
}

/**
 * Validate qc-sku setting
 * Ensures qc-sku cannot be enabled when enable-tote-scanning >= 1
 * Ensures qc-sku cannot be enabled when disable-QC-in-loading is true
 */
async function validateQcSku(
  setting: NewSetting,
  context: { domain: Domain; user: User }
): Promise<SettingValidationResult> {
  // Check if qc-sku is being set to true
  if (setting.value === 'true' || setting.value === true) {
    const enableToteScanningSetting: Setting = await getRepository(Setting).findOne({
      where: { domain: context.domain, name: 'enable-tote-scanning' }
    })

    if (enableToteScanningSetting) {
      const enableToteScanningValue = parseInt(enableToteScanningSetting.value || '0', 10)

      if (enableToteScanningValue >= 1) {
        return {
          valid: false,
          error:
            'qc-sku cannot be enabled when enable-tote-scanning is 1 or above. Please set enable-tote-scanning to 0 first.'
        }
      }
    }

    // Cross-validation: If qc-sku is being set to true, disable-QC-in-loading must be false
    const disableQcInLoadingSetting: Setting = await getRepository(Setting).findOne({
      where: { domain: context.domain, name: 'disable-QC-in-loading' }
    })

    if (
      disableQcInLoadingSetting &&
      (disableQcInLoadingSetting.value === 'true' || disableQcInLoadingSetting.value === true)
    ) {
      return {
        valid: false,
        error:
          'qc-sku cannot be enabled when disable-QC-in-loading is true. Please set disable-QC-in-loading to false first.'
      }
    }
  }

  return { valid: true }
}

/**
 * Validate update for qc-sku
 */
async function validateQcSkuUpdate(
  existingSetting: Setting,
  patch: SettingPatch,
  context: { domain: Domain; user: User }
): Promise<SettingValidationResult> {
  // Only validate if value is being updated
  if (patch.value === undefined) {
    return { valid: true }
  }

  // Check if qc-sku is being set to true
  if (patch.value === 'true' || patch.value === true) {
    const enableToteScanningSetting: Setting = await getRepository(Setting).findOne({
      where: { domain: context.domain, name: 'enable-tote-scanning' }
    })

    if (enableToteScanningSetting) {
      const enableToteScanningValue = parseInt(enableToteScanningSetting.value || '0', 10)

      if (enableToteScanningValue >= 1) {
        return {
          valid: false,
          error:
            'qc-sku cannot be enabled when enable-tote-scanning is 1 or above. Please set enable-tote-scanning to 0 first.'
        }
      }
    }

    // Cross-validation: If qc-sku is being set to true, disable-QC-in-loading must be false
    const disableQcInLoadingSetting: Setting = await getRepository(Setting).findOne({
      where: { domain: context.domain, name: 'disable-QC-in-loading' }
    })

    if (
      disableQcInLoadingSetting &&
      (disableQcInLoadingSetting.value === 'true' || disableQcInLoadingSetting.value === true)
    ) {
      return {
        valid: false,
        error:
          'qc-sku cannot be enabled when disable-QC-in-loading is true. Please set disable-QC-in-loading to false first.'
      }
    }
  }

  return await validateQcSku({ ...existingSetting, value: patch.value } as NewSetting, context)
}

/**
 * Validate disable-qc-in-loading setting
 * Ensures disable-qc-in-loading can only be set to true when enable-tote-scanning is 0 or 1
 * Ensures disable-qc-in-loading cannot be set to true when qc-sku is true
 */
async function validateDisableQcInLoading(
  setting: NewSetting,
  context: { domain: Domain; user: User }
): Promise<SettingValidationResult> {
  // Check if disable-qc-in-loading is being set to true
  if (setting.value === 'true' || setting.value === true) {
    const enableToteScanningSetting: Setting = await getRepository(Setting).findOne({
      where: { domain: context.domain, name: 'enable-tote-scanning' }
    })

    if (enableToteScanningSetting) {
      const enableToteScanningValue = parseInt(enableToteScanningSetting.value || '0', 10)

      // enable-tote-scanning must be 0 or 1 for disable-qc-in-loading to be true
      if (enableToteScanningValue > 1) {
        return {
          valid: false,
          error:
            'disable-qc-in-loading cannot be set to true when enable-tote-scanning is 2 or above. Please set enable-tote-scanning to 0 or 1 first.'
        }
      }
    }

    // Cross-validation: If disable-QC-in-loading is being set to true, qc-sku must be false
    const qcSkuSetting: Setting = await getRepository(Setting).findOne({
      where: { domain: context.domain, name: 'qc-sku' }
    })

    if (qcSkuSetting && (qcSkuSetting.value === 'true' || qcSkuSetting.value === true)) {
      return {
        valid: false,
        error: 'disable-QC-in-loading cannot be set to true when qc-sku is enabled. Please set qc-sku to false first.'
      }
    }
  }

  return { valid: true }
}

/**
 * Validate update for disable-qc-in-loading
 */
async function validateDisableQcInLoadingUpdate(
  existingSetting: Setting,
  patch: SettingPatch,
  context: { domain: Domain; user: User }
): Promise<SettingValidationResult> {
  // Only validate if value is being updated
  if (patch.value === undefined) {
    return { valid: true }
  }

  // Check if disable-qc-in-loading is being set to true
  if (patch.value === 'true' || patch.value === true) {
    const enableToteScanningSetting: Setting = await getRepository(Setting).findOne({
      where: { domain: context.domain, name: 'enable-tote-scanning' }
    })

    if (enableToteScanningSetting) {
      const enableToteScanningValue = parseInt(enableToteScanningSetting.value || '0', 10)

      // enable-tote-scanning must be 0 or 1 for disable-qc-in-loading to be true
      if (enableToteScanningValue > 1) {
        return {
          valid: false,
          error:
            'disable-qc-in-loading cannot be set to true when enable-tote-scanning is 2 or above. Please set enable-tote-scanning to 0 or 1 first.'
        }
      }
    }

    // Cross-validation: If disable-QC-in-loading is being set to true, qc-sku must be false
    const qcSkuSetting: Setting = await getRepository(Setting).findOne({
      where: { domain: context.domain, name: 'qc-sku' }
    })

    if (qcSkuSetting && (qcSkuSetting.value === 'true' || qcSkuSetting.value === true)) {
      return {
        valid: false,
        error: 'disable-QC-in-loading cannot be set to true when qc-sku is enabled. Please set qc-sku to false first.'
      }
    }
  }

  return await validateDisableQcInLoading({ ...existingSetting, value: patch.value } as NewSetting, context)
}

/**
 * Batch validator for qc-sku and disable-QC-in-loading
 * Validates that both settings cannot be set to true simultaneously
 */
async function validateQcSkuAndDisableQcInLoadingBatch(
  patches: SettingPatch[],
  existingSettings: Map<string, Setting>,
  context: { domain: Domain; user: User }
): Promise<SettingValidationResult> {
  // Find patches for qc-sku and disable-QC-in-loading
  let qcSkuPatch: SettingPatch | undefined
  let disableQcInLoadingPatch: SettingPatch | undefined
  let qcSkuExisting: Setting | undefined
  let disableQcInLoadingExisting: Setting | undefined

  for (const patch of patches) {
    // Try to find existing setting by id first, then by name
    let existing: Setting | undefined
    if (patch.id) {
      existing = existingSettings.get(patch.id)
    }

    // If not found by id, try to find by name in the patches
    if (!existing && patch.name) {
      // Look through existing settings map to find by name
      for (const setting of existingSettings.values()) {
        if (setting.name === patch.name) {
          existing = setting
          break
        }
      }
    }

    if (existing) {
      if (existing.name === 'qc-sku') {
        qcSkuPatch = patch
        qcSkuExisting = existing
      } else if (existing.name === 'disable-QC-in-loading') {
        disableQcInLoadingPatch = patch
        disableQcInLoadingExisting = existing
      }
    } else if (patch.name === 'qc-sku') {
      // Patch might be for a new setting or we need to fetch it
      qcSkuPatch = patch
      qcSkuExisting = await getRepository(Setting).findOne({
        where: { domain: context.domain, name: 'qc-sku' }
      })
    } else if (patch.name === 'disable-QC-in-loading') {
      disableQcInLoadingPatch = patch
      disableQcInLoadingExisting = await getRepository(Setting).findOne({
        where: { domain: context.domain, name: 'disable-QC-in-loading' }
      })
    }
  }

  // Get the effective values (new value from patch if present, otherwise existing value)
  const qcSkuValue = qcSkuPatch?.value !== undefined ? qcSkuPatch.value : qcSkuExisting?.value
  const disableQcInLoadingValue =
    disableQcInLoadingPatch?.value !== undefined ? disableQcInLoadingPatch.value : disableQcInLoadingExisting?.value

  // Check if both are being set to true simultaneously
  const qcSkuIsTrue = qcSkuValue === 'true' || qcSkuValue === true
  const disableQcInLoadingIsTrue = disableQcInLoadingValue === 'true' || disableQcInLoadingValue === true

  if (qcSkuIsTrue && disableQcInLoadingIsTrue) {
    return {
      valid: false,
      error:
        'qc-sku and disable-QC-in-loading cannot both be set to true simultaneously. Please set one to false first.'
    }
  }

  return { valid: true }
}

/**
 * Register all operato-wms validators
 * This function should be called during application bootstrap
 */
export function registerOperatoWmsSettingValidators(): void {
  // Register validators for specific setting names
  settingValidatorRegistry.registerCreateValidator('batch-picking-limit', validateBatchPickingLimit)
  settingValidatorRegistry.registerUpdateValidator('batch-picking-limit', validateBatchPickingLimitUpdate)

  settingValidatorRegistry.registerCreateValidator('stacking-option-limit', validateStackingOptionLimit)
  settingValidatorRegistry.registerUpdateValidator('stacking-option-limit', validateStackingOptionLimitUpdate)

  settingValidatorRegistry.registerCreateValidator('minimum-seal-number', validateMinimumSealNumber)
  settingValidatorRegistry.registerUpdateValidator('minimum-seal-number', validateMinimumSealNumberUpdate)

  settingValidatorRegistry.registerCreateValidator('enable-tote-scanning', validateEnableToteScanning)
  settingValidatorRegistry.registerUpdateValidator('enable-tote-scanning', validateEnableToteScanningUpdate)

  settingValidatorRegistry.registerCreateValidator('qc-sku', validateQcSku)
  settingValidatorRegistry.registerUpdateValidator('qc-sku', validateQcSkuUpdate)

  settingValidatorRegistry.registerCreateValidator('disable-QC-in-loading', validateDisableQcInLoading)
  settingValidatorRegistry.registerUpdateValidator('disable-QC-in-loading', validateDisableQcInLoadingUpdate)

  // Register batch validator for cross-validation between qc-sku and disable-QC-in-loading
  settingValidatorRegistry.registerBatchValidator(validateQcSkuAndDisableQcInLoadingBatch)

  // Register validators for location sorting rules using pattern matching
  settingValidatorRegistry.registerPatternValidator(
    (name: string) => name.startsWith('rule-for-'),
    validateLocationSortingRule,
    validateLocationSortingRule
  )

  // Register validators for boolean settings using pattern matching
  const booleanSettings = [
    'enable-bin-picking',
    'enable-product-scanning',
    'enable-beta-feature',
    'enable-carton-label',
    'enable-item-label',
    'hide-cross-dock',
    'disable-reusable-pallet',
    'enable-direct-activate-packing-worksheet',
    'enable-print-route-label',
    'enable-picking-random-seq',
    'enable-loading-select-all',
    'smart_batch_picking'
  ]

  booleanSettings.forEach(settingName => {
    settingValidatorRegistry.registerCreateValidator(settingName, validateBooleanSetting)
    settingValidatorRegistry.registerUpdateValidator(settingName, validateBooleanSetting)
  })
}
