import { isFuzzyMatch } from './fuzzy'

export interface DuplicateMatch {
  existingRecord: Record<string, unknown>
  incomingRecord: Record<string, unknown>
  matchType: 'exact' | 'high' | 'medium'
  matchedFields: string[]
  confidence: number
}

export interface DuplicateCheckConfig {
  entityType: string
  exactMatchFields: string[]
  fuzzyMatchFields: string[]
  compositeKeys: string[][]
}

function normalizeValue(v: unknown): string {
  if (v === null || v === undefined) return ''
  return String(v).toLowerCase().trim()
}

function matchType(confidence: number): 'exact' | 'high' | 'medium' {
  if (confidence >= 95) return 'exact'
  if (confidence >= 80) return 'high'
  return 'medium'
}

export function detectDuplicates(
  config: DuplicateCheckConfig,
  incomingRecords: Record<string, unknown>[],
  existingRecords: Record<string, unknown>[]
): Map<number, DuplicateMatch[]> {
  const result = new Map<number, DuplicateMatch[]>()

  for (let inIdx = 0; inIdx < incomingRecords.length; inIdx++) {
    const incoming = incomingRecords[inIdx]

    // We deduplicate by existingRecord reference: keep highest confidence per existing record
    const bestByExisting = new Map<Record<string, unknown>, { confidence: number; matchedFields: string[] }>()

    for (const existing of existingRecords) {
      let topConfidence = 0
      const matchedFields: string[] = []

      // 1. Exact match fields
      for (const field of config.exactMatchFields) {
        const iv = normalizeValue(incoming[field])
        const ev = normalizeValue(existing[field])
        if (iv === '' || ev === '') continue
        if (iv === ev) {
          if (topConfidence < 100) topConfidence = 100
          if (!matchedFields.includes(field)) matchedFields.push(field)
        }
      }

      // 2. Composite keys
      for (const group of config.compositeKeys) {
        const allMatch = group.every((field) => {
          const iv = normalizeValue(incoming[field])
          const ev = normalizeValue(existing[field])
          if (iv === '' || ev === '') return false
          return iv === ev
        })
        if (allMatch) {
          if (topConfidence < 95) topConfidence = Math.max(topConfidence, 95)
          for (const field of group) {
            if (!matchedFields.includes(field)) matchedFields.push(field)
          }
        }
      }

      // 3. Fuzzy match fields (threshold 0.7)
      let hasFuzzy = false
      for (const field of config.fuzzyMatchFields) {
        const iv = String(incoming[field] ?? '').trim()
        const ev = String(existing[field] ?? '').trim()
        if (iv === '' || ev === '') continue
        if (isFuzzyMatch(iv, ev, 0.7)) {
          hasFuzzy = true
          if (!matchedFields.includes(field)) matchedFields.push(field)
        }
      }

      if (hasFuzzy && topConfidence < 95) {
        // If fuzzy + some composite partial (already had composite confidence), upgrade to 80
        // otherwise 60
        const newConf = topConfidence >= 60 ? 80 : 60
        if (newConf > topConfidence) topConfidence = newConf
      }

      if (topConfidence >= 60 && matchedFields.length > 0) {
        const prev = bestByExisting.get(existing)
        if (!prev || topConfidence > prev.confidence) {
          bestByExisting.set(existing, { confidence: topConfidence, matchedFields: [...matchedFields] })
        }
      }
    }

    if (bestByExisting.size > 0) {
      const matches: DuplicateMatch[] = []
      for (const [existingRecord, { confidence, matchedFields }] of bestByExisting) {
        matches.push({
          existingRecord,
          incomingRecord: incoming,
          matchType: matchType(confidence),
          matchedFields,
          confidence,
        })
      }
      result.set(inIdx, matches)
    }
  }

  return result
}
