import type { ReservationRecord, ReservationTaskFieldResult } from '@/types/reservation'

const truthyRuleValues = new Set(['Y', 'YES', 'TRUE', '1', '是', '可编辑', 'ENABLED'])
const falsyRuleValues = new Set(['N', 'NO', 'FALSE', '0', '否', '不可编辑', 'DISABLED', '隐藏'])
const roomItemFieldPathAliases: Record<string, string[]> = {
  'extracted_fields.room_quantity': ['extracted_fields.room_items.0.room_quantity'],
  'extracted_fields.room_type': [
    'extracted_fields.room_items.0.room_type_raw',
    'extracted_fields.room_items.0.room_type_normalized',
  ],
  'extracted_fields.pms_room_type_code': ['extracted_fields.room_items.0.pms_room_type_code'],
  'extracted_fields.room_items.0.room_quantity': ['extracted_fields.room_quantity'],
  'extracted_fields.room_items.0.room_type_raw': [
    'extracted_fields.room_type',
    'extracted_fields.room_items.0.room_type_normalized',
  ],
  'extracted_fields.room_items.0.pms_room_type_code': ['extracted_fields.pms_room_type_code'],
}

export interface FieldAreaGroup {
  area: string
  fields: ReservationTaskFieldResult[]
}

export interface EnumOption {
  value: string
  label: string
}

export type ReservationFieldValidationErrorCode = 'required' | 'enum' | 'date' | 'number'

export interface ReservationFieldValidationError {
  code: ReservationFieldValidationErrorCode
  field_name: string
}

export type ReservationFieldValidationErrors = Record<string, ReservationFieldValidationError>

export function groupReservationFields(
  fields: ReservationTaskFieldResult[],
  fallbackArea: string,
  values: ReservationRecord = {},
): FieldAreaGroup[] {
  const grouped = new Map<string, ReservationTaskFieldResult[]>()
  fields.filter((field) => isReservationFieldActive(field, values)).forEach((field) => {
    const area = field.display_area?.trim() || fallbackArea
    grouped.set(area, [...(grouped.get(area) ?? []), field])
  })

  return Array.from(grouped.entries()).map(([area, areaFields]) => ({
    area,
    fields: areaFields.sort((left, right) => (left.row_number ?? 0) - (right.row_number ?? 0)),
  }))
}

export function isReservationFieldVisible(field: ReservationTaskFieldResult): boolean {
  return !isRuleFalsy(field.visible)
}

export function isReservationFieldActive(field: ReservationTaskFieldResult, values: ReservationRecord = {}): boolean {
  if (!isReservationFieldVisible(field)) {
    return false
  }
  const condition = field.display_condition?.trim()
  if (!condition || condition === '-' || condition.includes('始终展示')) {
    return true
  }
  const currentValue = valueForConditionPath(values, field, field.field_path)
  if (condition.includes('有值时展示')) {
    return !isEmptyReservationValue(currentValue)
  }
  if (condition.includes('有附件')) {
    return !isEmptyReservationValue(valueForConditionPath(values, field, 'attachments'))
  }
  if (condition.includes('对象为FIT Reservation')) {
    return isFitTarget(values, field)
  }
  if (condition.includes('对象为Group Block或Allotment')) {
    return isGroupOrAllotmentTarget(values, field)
  }
  if (condition.includes('涉及房型')) {
    return (
      matrixContains(field.task_type, 'New Booking') ||
      !isEmptyReservationValue(valueForConditionPath(values, field, 'extracted_fields.room_type')) ||
      !isEmptyReservationValue(valueForConditionPath(values, field, 'extracted_fields.pms_room_type_code'))
    )
  }
  if (condition.includes('涉及Rate Code') || condition.includes('涉及价格/Rate Code')) {
    return (
      !isEmptyReservationValue(valueForConditionPath(values, field, 'extracted_fields.rate_code_result')) ||
      hasNonEmptyValueWithPrefix(values, 'extracted_fields.rate_code_result.') ||
      matrixContains(field.task_subtype, 'manual_rate_code_or_settlement_price')
    )
  }
  if (condition.includes('需要价格确认')) {
    return !isEmptyReservationValue(valueForConditionPath(values, field, 'extracted_fields.rate_code_result.settlement_price'))
  }
  if (condition.includes('有复合单价') || condition.includes('fix_charge_required=true')) {
    return (
      isBooleanTrue(valueForConditionPath(values, field, 'extracted_fields.fix_charge_required')) ||
      !isEmptyReservationValue(valueForConditionPath(values, field, 'extracted_fields.fix_charge_items[]'))
    )
  }
  if (condition.includes('allocation_split_from_parent=true')) {
    return isBooleanTrue(valueForConditionPath(values, field, 'extracted_fields.allocation_split_from_parent'))
  }
  if (condition.includes('来自QBD/LianTai表格')) {
    return !isEmptyReservationValue(valueForConditionPath(values, field, 'extracted_fields.table_evidence'))
  }
  if (condition.includes('有Group目标')) {
    return !isEmptyReservationValue(valueForConditionPath(values, field, 'case_keys.group_code')) || isGroupOrAllotmentTarget(values, field)
  }
  if (condition.includes('有FIT目标') || condition.includes('FIT取消')) {
    return !isEmptyReservationValue(valueForConditionPath(values, field, 'case_keys.confirmation_number')) || isFitTarget(values, field)
  }
  if (condition.includes('有before/after修改')) {
    return !isEmptyReservationValue(valueForConditionPath(values, field, 'extracted_fields.before_after'))
  }
  if (condition.includes('update_fix_charge')) {
    return matrixContains(field.task_subtype, 'update_fix_charge') || isBooleanTrue(valueForConditionPath(values, field, 'extracted_fields.fix_charge_required'))
  }
  return true
}

export function isReservationFieldRequired(field: ReservationTaskFieldResult): boolean {
  return isRuleTruthy(field.required_rule)
}

export function isReservationFieldEditable(field: ReservationTaskFieldResult, readOnly: boolean): boolean {
  if (readOnly) {
    return false
  }
  return (
    isRuleTruthy(field.editable) ||
    isRuleTruthy(field.input_editable) ||
    isRuleTruthy(field.select_editable) ||
    isRuleTruthy(field.date_picker) ||
    isRuleTruthy(field.number_input) ||
    isRuleTruthy(field.table_editable)
  )
}

export function isSelectField(field: ReservationTaskFieldResult): boolean {
  return isRuleTruthy(field.select_editable) && parseEnumOptions(field.enum_options).length > 0
}

export function isDateField(field: ReservationTaskFieldResult): boolean {
  return isRuleTruthy(field.date_picker)
}

export function isNumberField(field: ReservationTaskFieldResult): boolean {
  return isRuleTruthy(field.number_input)
}

export function isFileField(field: ReservationTaskFieldResult): boolean {
  return isRuleTruthy(field.file_display)
}

export function isTableField(field: ReservationTaskFieldResult): boolean {
  return isRuleTruthy(field.table_editable)
}

export function parseEnumOptions(enumOptions: string | null): EnumOption[] {
  if (!enumOptions?.trim()) {
    return []
  }

  return enumOptions
    .split(/[,，;；/]/)
    .map((item) => item.trim())
    .filter(Boolean)
    .map((item) => {
      const separatorIndex = item.indexOf(':')
      const value = separatorIndex >= 0 ? item.slice(0, separatorIndex) : item
      const label = separatorIndex >= 0 ? item.slice(separatorIndex + 1) : value
      return {
        value: value.trim(),
        label: label.trim(),
      }
    })
}

export function buildInitialFieldValues(
  fields: ReservationTaskFieldResult[],
  payload: ReservationRecord | null,
  fieldContractVersion?: string | null,
): ReservationRecord {
  return fields.reduce<ReservationRecord>((values, field) => {
    values[field.field_path] = readReservationFieldValue(field, payload ?? {}, fieldContractVersion)
    return values
  }, {})
}

export function buildEditableFieldValues(
  fields: ReservationTaskFieldResult[],
  values: ReservationRecord,
  readOnly: boolean,
): ReservationRecord {
  if (readOnly) {
    return {}
  }
  return fields
    .filter((field) => isReservationFieldActive(field, values))
    .filter((field) => isReservationFieldEditable(field, readOnly))
    .reduce<ReservationRecord>((payload, field) => {
      payload[field.field_path] = readReservationFieldValue(field, values)
      return payload
    }, {})
}

export function validateReservationFieldValues(
  fields: ReservationTaskFieldResult[],
  values: ReservationRecord,
): ReservationFieldValidationErrors {
  return fields.filter((field) => isReservationFieldActive(field, values)).reduce<ReservationFieldValidationErrors>((errors, field) => {
    const value = readReservationFieldValue(field, values)
    const shouldValidateInput = isReservationFieldEditable(field, false)
    if (shouldValidateInput && isReservationFieldRequired(field) && isEmptyReservationValue(value)) {
      errors[field.field_path] = createValidationError(field, 'required')
      return errors
    }

    if (!shouldValidateInput || isEmptyReservationValue(value)) {
      return errors
    }

    if (isSelectField(field) && !parseEnumOptions(field.enum_options).some((option) => option.value === String(value))) {
      errors[field.field_path] = createValidationError(field, 'enum')
      return errors
    }

    if (isDateField(field) && !isIsoLocalDate(String(value))) {
      errors[field.field_path] = createValidationError(field, 'date')
      return errors
    }

    if (isNumberField(field) && !Number.isFinite(Number(value))) {
      errors[field.field_path] = createValidationError(field, 'number')
    }
    return errors
  }, {})
}

function valueForConditionPath(
  values: ReservationRecord,
  currentField: ReservationTaskFieldResult,
  fieldPath: string,
): unknown {
  const value = readValueByFieldPath(values, fieldPath)
  if (value !== undefined) {
    return value
  }
  if (fieldPath === currentField.field_path) {
    return currentField.value
  }
  return undefined
}

function hasNonEmptyValueWithPrefix(values: ReservationRecord, prefix: string): boolean {
  return Object.entries(values).some(([key, value]) => key.startsWith(prefix) && !isEmptyReservationValue(value))
}

function isFitTarget(values: ReservationRecord, field: ReservationTaskFieldResult): boolean {
  const target = stringifyConditionValue(valueForConditionPath(values, field, 'extracted_fields.booking_object_type'))
  return target.includes('FIT') || target.includes('RESERVATION')
}

function isGroupOrAllotmentTarget(values: ReservationRecord, field: ReservationTaskFieldResult): boolean {
  const target = stringifyConditionValue(valueForConditionPath(values, field, 'extracted_fields.booking_object_type'))
  return target.includes('GROUP') || target.includes('ALLOTMENT') || !isEmptyReservationValue(valueForConditionPath(values, field, 'case_keys.group_code'))
}

function stringifyConditionValue(value: unknown): string {
  return value === null || value === undefined ? '' : String(value).trim().toUpperCase()
}

function matrixContains(value: string | null | undefined, keyword: string): boolean {
  return Boolean(value?.toLowerCase().includes(keyword.toLowerCase()))
}

function isBooleanTrue(value: unknown): boolean {
  return value === true || String(value).trim().toLowerCase() === 'true' || String(value).trim() === '是'
}

function createValidationError(
  field: ReservationTaskFieldResult,
  code: ReservationFieldValidationErrorCode,
): ReservationFieldValidationError {
  return {
    code,
    field_name: field.display_name,
  }
}

export function stringifyReservationValue(value: unknown, emptyLabel: string): string {
  if (value === null || value === undefined || value === '') {
    return emptyLabel
  }
  if (Array.isArray(value)) {
    return value.map((item) => stringifyReservationValue(item, emptyLabel)).join(', ')
  }
  if (typeof value === 'object') {
    return JSON.stringify(value)
  }
  return String(value)
}

function isEmptyReservationValue(value: unknown): boolean {
  if (value === null || value === undefined) {
    return true
  }
  if (typeof value === 'string') {
    return value.trim() === ''
  }
  if (Array.isArray(value)) {
    return value.length === 0
  }
  return false
}

function isIsoLocalDate(value: string): boolean {
  if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
    return false
  }
  const date = new Date(`${value}T00:00:00Z`)
  return !Number.isNaN(date.getTime()) && date.toISOString().slice(0, 10) === value
}

export function readReservationFieldValue(
  field: ReservationTaskFieldResult,
  values: ReservationRecord,
  fieldContractVersion?: string | null,
): unknown {
  const fieldValues = recordProperty(values, 'field_values') ?? values
  const legacyValues = recordProperty(values, 'legacy_field_values')
  const effectivePayload = recordProperty(values, 'effective_payload')
  const legacyFieldPath = field.legacy_field_path ?? undefined
  const codeV1 = normalizeContractVersion(fieldContractVersion) === 'CODE_V1'
  const mainCandidates = fieldPathCandidates(field.field_path)
  const legacyCandidates = legacyFieldPath ? fieldPathCandidates(legacyFieldPath) : []

  if (codeV1) {
    return firstDefined([
      () => firstValueByPaths(legacyValues, legacyCandidates),
      () => firstValueByPaths(fieldValues, legacyCandidates),
      () => firstNestedValueByPaths(fieldValues, legacyCandidates),
      () => firstValueByPaths(values, legacyCandidates),
      () => firstNestedValueByPaths(effectivePayload, legacyCandidates),
      () => firstValueByPaths(fieldValues, mainCandidates),
      () => firstNestedValueByPaths(fieldValues, mainCandidates),
      () => firstValueByPaths(values, mainCandidates),
      () => firstNestedValueByPaths(effectivePayload, mainCandidates),
      () => field.value,
      () => '',
    ])
  }

  return firstDefined([
    () => firstValueByPaths(fieldValues, mainCandidates),
    () => firstNestedValueByPaths(fieldValues, mainCandidates),
    () => firstValueByPaths(values, mainCandidates),
    () => firstNestedValueByPaths(effectivePayload, mainCandidates),
    () => firstValueByPaths(legacyValues, legacyCandidates),
    () => firstValueByPaths(fieldValues, legacyCandidates),
    () => firstNestedValueByPaths(fieldValues, legacyCandidates),
    () => firstValueByPaths(values, legacyCandidates),
    () => field.value,
    () => '',
  ])
}

function readValueByFieldPath(values: ReservationRecord, fieldPath: string): unknown {
  const flatValue = firstValueByPaths(values, fieldPathCandidates(fieldPath))
  if (flatValue !== undefined) {
    return flatValue
  }
  return readNestedPath(values, fieldPath)
}

function fieldPathCandidates(fieldPath: string): string[] {
  return [fieldPath, ...(roomItemFieldPathAliases[fieldPath] ?? [])]
}

function firstValueByPaths(values: ReservationRecord | undefined, fieldPaths: string[]): unknown {
  if (!values) {
    return undefined
  }
  for (const fieldPath of fieldPaths) {
    if (Object.prototype.hasOwnProperty.call(values, fieldPath)) {
      return values[fieldPath]
    }
  }
  return undefined
}

function firstNestedValueByPaths(values: ReservationRecord | undefined, fieldPaths: string[]): unknown {
  if (!values) {
    return undefined
  }
  for (const fieldPath of fieldPaths) {
    const value = readNestedPath(values, fieldPath)
    if (value !== undefined) {
      return value
    }
  }
  return undefined
}

function readNestedPath(values: ReservationRecord, fieldPath: string): unknown {
  return fieldPath.split('.').reduce<unknown>((current, segment) => {
    if (current === null || current === undefined) {
      return undefined
    }
    if (Array.isArray(current)) {
      const index = Number(segment)
      return Number.isInteger(index) ? current[index] : undefined
    }
    if (typeof current === 'object' && Object.prototype.hasOwnProperty.call(current, segment)) {
      return (current as ReservationRecord)[segment]
    }
    return undefined
  }, values)
}

function firstDefined(candidates: Array<() => unknown>): unknown {
  for (const candidate of candidates) {
    const value = candidate()
    if (value !== undefined && value !== null) {
      return value
    }
  }
  return ''
}

function recordProperty(values: ReservationRecord, key: string): ReservationRecord | undefined {
  const value = values[key]
  return value && typeof value === 'object' && !Array.isArray(value) ? value as ReservationRecord : undefined
}

function normalizeContractVersion(version: string | null | undefined): string {
  return version?.trim().replace(/[\s-]+/g, '_').toUpperCase() ?? ''
}

function isRuleTruthy(value: string | null): boolean {
  if (!value) {
    return false
  }
  return truthyRuleValues.has(value.trim().toUpperCase()) || truthyRuleValues.has(value.trim())
}

function isRuleFalsy(value: string | null): boolean {
  if (!value) {
    return false
  }
  return falsyRuleValues.has(value.trim().toUpperCase()) || falsyRuleValues.has(value.trim())
}
