import type { Need } from '../../domain/need'
import type { Property } from '../../domain/property'
import type { ScoreFactor } from '../../domain/match'
import { ResultType, AvailabilityStatus, AssetType } from '../../domain/enums'
import {
  HARD_FILTER,
  DATA_QUALITY_MODIFIER,
  CONFIDENCE_MODIFIER,
  HARD_CRITERION_KEYS,
  SOFT_FACTOR_KEYS,
  DEFAULT_SCORING_PROFILES,
} from '../../domain/scoring'
import type { ScoringWeightProfile, HardFilterResult, MatchEngineOutput, SoftFactorKey } from '../../domain/scoring'
import { analyzeTradeOffs, analyzeRisks, identifyMissingData } from './tradeOffAnalyzer'
import { generateNextBestActions } from './rankingEngine'
import { softFactorEnrichmentService } from '../../services/softFactorEnrichmentService'
import { scoreMustHaves } from './mustHaveScorer'
import { effectiveAnnualBurdenPerSqm } from '../../lib/fitOutUtils'

// ── Profile resolution ────────────────────────────────────────────────────────

function resolveProfile(need: Need, property: Property): ScoringWeightProfile {
  const base = { ...(DEFAULT_SCORING_PROFILES[property.assetType] ?? DEFAULT_SCORING_PROFILES.DEFAULT) }
  const np = need.weightingProfile
  if (!np) return base

  // Apply need's custom core weights, then renormalize the full profile to 1.00
  const CORE = [
    'area', 'location', 'budget', 'timing',
    'prestige', 'accessibility', 'expansionPotential', 'flexibility',
    'visibility', 'footfall', 'talentAccess', 'esg', 'taxEnvironment',
  ]
  for (const key of CORE) {
    if (typeof np[key] === 'number') base[key] = np[key]
  }
  const total = Object.values(base).reduce((s, v) => s + v, 0)
  if (total > 0) for (const key of Object.keys(base)) base[key] /= total
  return base
}

// ── Hard Filters ──────────────────────────────────────────────────────────────

export function applyHardFilters(need: Need, property: Property): HardFilterResult {
  const assetOk = property.assetType === need.assetType
    || property.assetType === AssetType.MIXED
    || need.assetType === AssetType.UNKNOWN

  if (!assetOk) {
    return { excluded: true, reason: `Nutzungstyp ${property.assetType} stimmt nicht mit ${need.assetType} überein`, severePenalty: 0 }
  }

  // Area: hard exclude below tolerance
  const areaMin = need.requiredArea?.min ?? 0
  const propArea = property.areaSqmMin ?? property.areaSqm
  if (areaMin > 0 && propArea < areaMin * HARD_FILTER.AREA_MIN_TOLERANCE) {
    return {
      excluded: true,
      reason: `Fläche ${propArea} m² unterschreitet Minimum ${areaMin} m² um mehr als ${Math.round((1 - HARD_FILTER.AREA_MIN_TOLERANCE) * 100)}%`,
      severePenalty: 0,
    }
  }

  // Region exclusion
  const city = property.location.city.toLowerCase()
  const excluded = (need.excludedLocations ?? []).map(l => l.toLowerCase())
  if (excluded.some(e => city.includes(e) || e.includes(city))) {
    return { excluded: true, reason: `Standort ${property.location.city} ist ausgeschlossen`, severePenalty: 0 }
  }

  // Budget: hard exclude if massively over
  const maxBudget = need.budgetRange?.maxPerSqm ?? 0
  if (maxBudget > 0 && property.rentPricePerSqm > maxBudget * HARD_FILTER.BUDGET_EXCLUSION_RATIO) {
    return {
      excluded: true,
      reason: `Miete CHF ${property.rentPricePerSqm}/m² überschreitet Budget CHF ${maxBudget}/m² um mehr als ${Math.round((HARD_FILTER.BUDGET_EXCLUSION_RATIO - 1) * 100)}%`,
      severePenalty: 0,
    }
  }

  // Usage/zoning: occupied property is severe penalty, not exclude
  if (property.availabilityStatus === AvailabilityStatus.OCCUPIED) {
    return { excluded: false, reason: undefined, severePenalty: 25 }
  }

  return { excluded: false, reason: undefined, severePenalty: 0 }
}

// ── Hard Criterion Scorers ────────────────────────────────────────────────────

function scoreArea(need: Need, property: Property, weight: number): ScoreFactor {
  const { min = 0, max = Infinity } = need.requiredArea ?? {}
  const area = property.areaSqm
  const areaMin = property.areaSqmMin ?? area
  const areaMax = property.areaSqmMax ?? area

  let score: number
  let explanation: string

  // Flexible property — check range overlap
  const overlap = areaMin <= max && areaMax >= min
  if (overlap) {
    score = 100
    explanation = `Fläche ${areaMin === areaMax ? `${area}` : `${areaMin}–${areaMax}`} m² deckt Bedarf ${min}–${max} m² ab`
  } else if (area > max) {
    const ratio = area / max
    score = ratio <= HARD_FILTER.AREA_MAX_RATIO
      ? Math.max(40, Math.round(100 - (ratio - 1) * 50))
      : 20
    explanation = `Fläche ${area} m² überschreitet Maximum ${max} m² (${Math.round((ratio - 1) * 100)}% zu viel)`
  } else {
    // Area between tolerance and min — mild penalty
    const ratio = area / min
    score = Math.round(40 + ratio * 30)
    explanation = `Fläche ${area} m² leicht unter Minimum ${min} m²`
  }

  return { criterion: 'area', weight, score, contribution: score * weight, explanation }
}

function scoreLocation(need: Need, property: Property, weight: number): ScoreFactor {
  const city = property.location.city.toLowerCase()
  const district = (property.location.district ?? '').toLowerCase()
  const canton = (property.location.canton ?? '').toLowerCase()
  const preferred = (need.preferredLocations ?? []).map(l => l.toLowerCase())

  let score: number
  let explanation: string

  if (preferred.length === 0) {
    score = 80
    explanation = 'Kein Standortwunsch — flexibel bewertet'
  } else if (district && preferred.some(p => {
    // "Zürich Kreis 5".includes("Kreis 5") or pSuffix "kreis 5" ⊂ district
    if (p.includes(district)) return true
    const pSuffix = p.replace(/^zürich\s+/i, '').trim()
    // Only use pSuffix when the regex actually removed a prefix (avoids 'zürich' falsely
    // matching district names like 'Zürich-West' because 'Zürich-West'.includes('zürich'))
    return pSuffix !== p && pSuffix.length > 0 && district.includes(pSuffix)
  })) {
    score = 100
    explanation = `Bezirk ${property.location.district} entspricht Präferenz`
  } else if (preferred.some(p => city.includes(p) || p.includes(city))) {
    // City matches but check if preferred has district-specific entries for this city
    // → penalise when a specific district was requested but this property is in a different one
    const hasDistrictSpecifics = preferred.some(p => p.includes(city) && p.length > city.length + 2)
    score = hasDistrictSpecifics ? 70 : 100
    explanation = score === 100
      ? `Standort ${property.location.city} entspricht Präferenz`
      : `${property.location.city}${property.location.district ? ` (${property.location.district})` : ''} — Lage akzeptiert, bevorzugter Bezirk nicht erfüllt`
  } else if (canton && preferred.some(p => p.includes(canton) || canton.includes(p))) {
    score = 60
    explanation = `Gleicher Kanton wie Präferenz (${property.location.canton})`
  } else {
    score = 35
    explanation = `Standort ${property.location.city} nicht in Präferenzliste`
  }

  return { criterion: 'location', weight, score, contribution: score * weight, explanation }
}

function scoreBudget(need: Need, property: Property, weight: number): ScoreFactor {
  const maxBudget = need.budgetRange?.maxPerSqm ?? 0

  // Effektive Jahresbelastung: Kaltmiete + annuitätischer Ausbau-Aufschlag (nur wenn Mieter ausbaut)
  const { effectivePerSqm: rent, fitOutPerSqm } = effectiveAnnualBurdenPerSqm({
    fitOut: property.hardFacts?.fitOut,
    rentPricePerSqm: property.rentPricePerSqm,
    mabPerSqm: property.hardFacts?.mieterausbaubeitragPerSqm ?? 0,
    fitOutByLandlord: property.hardFacts?.fitOutByLandlord,
  })
  // Erklärt den Ausbau-Anteil transparent, wenn er den Score beeinflusst
  const fitOutNote = fitOutPerSqm > 0 ? ` (inkl. CHF ${fitOutPerSqm}/m² Ausbau-Annuität)` : ''

  let score: number
  let explanation: string

  if (maxBudget <= 0) {
    score = 60
    explanation = 'Kein Budget angegeben — neutral bewertet'
  } else if (rent <= maxBudget) {
    const ratio = rent / maxBudget
    // Very cheap can indicate quality issues — slight penalty below 50% of budget
    score = ratio >= 0.50 ? 100 : 88
    explanation = `Effektive Belastung CHF ${rent}/m² liegt ${Math.round((1 - ratio) * 100)}% unter Budget CHF ${maxBudget}/m²${fitOutNote}`
  } else {
    const overRatio = rent / maxBudget
    if (overRatio <= HARD_FILTER.BUDGET_MODERATE_RATIO) {
      score = 75
      explanation = `Effektive Belastung CHF ${rent}/m² leicht über Budget (+${Math.round((overRatio - 1) * 100)}%)${fitOutNote}`
    } else if (overRatio <= HARD_FILTER.BUDGET_SEVERE_RATIO) {
      score = 45
      explanation = `Effektive Belastung CHF ${rent}/m² merklich über Budget (+${Math.round((overRatio - 1) * 100)}%)${fitOutNote}`
    } else {
      score = 20
      explanation = `Effektive Belastung CHF ${rent}/m² stark über Budget (+${Math.round((overRatio - 1) * 100)}%)${fitOutNote}`
    }
  }

  return { criterion: 'budget', weight, score, contribution: score * weight, explanation }
}

function scoreTiming(need: Need, property: Property, weight: number): ScoreFactor {
  const isFutureSignal = property.resultType === ResultType.FUTURE_AVAILABILITY

  const rawDate = property.availabilityDate
  const propDate = rawDate && rawDate !== '' ? new Date(rawDate) : null
  const earliest = need.timing?.earliestMoveIn ? new Date(need.timing.earliestMoveIn) : null
  const latest = need.timing?.latestMoveIn ? new Date(need.timing.latestMoveIn) : null
  const graceMs = HARD_FILTER.TIMING_GRACE_DAYS * 86_400_000

  let score: number
  let explanation: string

  // CRITICAL RULE: Future availability is never treated as confirmed
  if (isFutureSignal) {
    if (!propDate) {
      score = 30
      explanation = 'Zukünftiges Signal — kein Datum, Verfügbarkeit unbestätigt'
    } else if (latest && propDate.getTime() > latest.getTime() + graceMs) {
      score = 20
      explanation = `Zukünftiges Signal — erwartet ${rawDate}, nach gewünschtem Zeitfenster (unbestätigt)`
    } else if (earliest && propDate.getTime() < earliest.getTime()) {
      score = 50
      explanation = `Zukünftiges Signal — erwartet ${rawDate}, vor gewünschtem Einzug (unbestätigt)`
    } else {
      score = 42
      explanation = `Zukünftiges Signal — Zeitfenster passt, Verfügbarkeit jedoch unbestätigt`
    }
    return { criterion: 'timing', weight, score, contribution: score * weight, explanation }
  }

  const isNow = property.availabilityStatus === AvailabilityStatus.AVAILABLE_NOW
    || property.availabilityStatus === AvailabilityStatus.AVAILABLE_SOON

  if (isNow) {
    const tooEarly = earliest && new Date() < earliest
    score = tooEarly ? 80 : 100
    explanation = tooEarly
      ? `Sofort verfügbar — Einzug jedoch erst ab ${need.timing?.earliestMoveIn} geplant`
      : 'Sofort verfügbar — entspricht Verfügbarkeitswunsch'
    return { criterion: 'timing', weight, score, contribution: score * weight, explanation }
  }

  if (!propDate) {
    score = 38
    explanation = 'Kein Verfügbarkeitsdatum angegeben'
    return { criterion: 'timing', weight, score, contribution: score * weight, explanation }
  }

  if (earliest && latest) {
    const t = propDate.getTime()
    if (t >= earliest.getTime() && t <= latest.getTime()) {
      score = 95
      explanation = `Verfügbar ${rawDate} liegt im Einzugsfenster`
    } else if (t < earliest.getTime()) {
      const diff = earliest.getTime() - t
      score = diff < graceMs ? 80 : 65
      explanation = `Verfügbar ${rawDate} vor gewünschtem Einzug — kurze Leerstandszeit`
    } else {
      const diff = t - latest.getTime()
      score = diff < graceMs ? 55 : 28
      explanation = `Verfügbar ${rawDate} nach gewünschtem Zeitfenster`
    }
  } else {
    score = 65
    explanation = `Verfügbar ${rawDate}`
  }

  return { criterion: 'timing', weight, score, contribution: score * weight, explanation }
}

// ── Soft Factor Scorer ────────────────────────────────────────────────────────

function scoreSoftFactor(key: SoftFactorKey, weight: number, property: Property): ScoreFactor {
  const sf = property.softFactors
  const hf = property.hardFacts

  const rawValue = (() => {
    switch (key) {
      case 'prestige':           return sf?.prestigeScore ?? sf?.prestige
      case 'accessibility':      return sf?.commuterAccessScore ?? sf?.accessibility
        ?? (hf?.publicTransportScore !== undefined ? hf.publicTransportScore / 10 : undefined)
      case 'expansionPotential': return sf?.expansionPotentialScore
      case 'flexibility':        return sf?.flexibilityScore
      case 'visibility':         return sf?.visibilityScore
      case 'footfall': {
        if (sf?.footfallScore !== undefined) return sf.footfallScore
        // Map passerbyFrequency string (RETAIL properties) to numeric score
        const pfMap: Record<string, number> = { HIGH: 85, MEDIUM_HIGH: 68, MEDIUM: 50, LOW: 30 }
        const pf = (sf as { passerbyFrequency?: string } | undefined)?.passerbyFrequency
        return pf !== undefined ? (pfMap[pf] ?? 50) : undefined
      }
      case 'talentAccess':       return sf?.talentAccessScore ?? sf?.talentAccess
      case 'esg':                return sf?.esgScore
      case 'taxEnvironment':     return sf?.taxEnvironmentScore
      default:                   return undefined
    }
  })()

  if (rawValue === undefined || rawValue === null) {
    // No direct data — try location-intelligence estimate
    const est = softFactorEnrichmentService.estimate(key, property)
    if (est) {
      const score = Math.round(Math.min(100, Math.max(0, est.score * 100)))
      const LABELS: Record<SoftFactorKey, string> = {
        prestige: 'Prestige', accessibility: 'Erreichbarkeit', expansionPotential: 'Expansion',
        flexibility: 'Flexibilität', visibility: 'Sichtbarkeit', footfall: 'Passantenfrequenz',
        talentAccess: 'Talent-Zugang', esg: 'ESG', taxEnvironment: 'Steuerumfeld',
      }
      return {
        criterion: key,
        weight,
        score,
        contribution: score * weight,
        explanation: `${LABELS[key] ?? key}: ${score}/100 — Schätzung basierend auf Standort (${est.label})`,
        estimated: true,
      }
    }
    return {
      criterion: key,
      weight,
      score: 50,
      contribution: 50 * weight,
      explanation: `${key}: keine Daten verfügbar — neutral bewertet`,
    }
  }

  // Support both 0–1 float scale (enrichment estimates) and 0–100 integer scale (mock data)
  const score = rawValue > 1
    ? Math.round(Math.min(100, Math.max(0, rawValue)))
    : Math.round(Math.min(100, Math.max(0, rawValue * 100)))
  const LABELS: Record<SoftFactorKey, string> = {
    prestige: 'Prestige', accessibility: 'Erreichbarkeit', expansionPotential: 'Expansion',
    flexibility: 'Flexibilität', visibility: 'Sichtbarkeit', footfall: 'Passantenfrequenz',
    talentAccess: 'Talent-Zugang', esg: 'ESG', taxEnvironment: 'Steuerumfeld',
  }
  return {
    criterion: key,
    weight,
    score,
    contribution: score * weight,
    explanation: `${LABELS[key] ?? key}: ${score}/100`,
  }
}

// ── Structured Requirement Scorers ───────────────────────────────────────────

const FIT_OUT_LEVELS: Record<string, number> = { SHELL: 0, BASIC: 1, FULL: 2, PREMIUM: 3 }

function scoreGroundFloor(need: Need, property: Property): ScoreFactor | null {
  if (!need.requireGroundFloor) return null
  const floor = property.hardFacts?.floor ?? property.floorLevel
  if (floor === undefined) {
    return { criterion: 'groundFloor', weight: 0.05, score: 50, contribution: 2.5, explanation: 'Erdgeschoss erforderlich — Stockwerk nicht dokumentiert', estimated: true }
  }
  const passed = floor === 0
  const score = passed ? 100 : 20
  return {
    criterion: 'groundFloor',
    weight: 0.05,
    score,
    contribution: score * 0.05,
    explanation: passed ? `Erdgeschoss bestätigt (Etage ${floor})` : `Nicht Erdgeschoss (Etage ${floor}) — EG erforderlich`,
  }
}

function scoreParkingMin(need: Need, property: Property): ScoreFactor | null {
  const min = need.requiredParkingMin
  if (!min || min <= 0) return null
  const available = property.hardFacts?.parking ?? property.softFactors?.parkingSpots ?? 0
  if (available === 0 && property.hardFacts?.parking === undefined) {
    return { criterion: 'parkingMin', weight: 0.04, score: 50, contribution: 2, explanation: `Mind. ${min} Parkplätze erforderlich — keine Daten`, estimated: true }
  }
  const ratio = Math.min(1, available / min)
  const score = available >= min ? 100 : Math.round(ratio * 60)
  return {
    criterion: 'parkingMin',
    weight: 0.04,
    score,
    contribution: score * 0.04,
    explanation: available >= min
      ? `${available} Parkplätze vorhanden (mind. ${min} erforderlich)`
      : `Nur ${available} Parkplätze (mind. ${min} erforderlich)`,
  }
}

function scoreAirConditioning(need: Need, property: Property): ScoreFactor | null {
  if (!need.requireAirConditioning) return null
  const hasAC = property.hardFacts?.hasAirConditioning
  if (hasAC === undefined) {
    return { criterion: 'airConditioning', weight: 0.03, score: 50, contribution: 1.5, explanation: 'Klimaanlage erforderlich — nicht dokumentiert', estimated: true }
  }
  const score = hasAC ? 100 : 15
  return {
    criterion: 'airConditioning',
    weight: 0.03,
    score,
    contribution: score * 0.03,
    explanation: hasAC ? 'Klimaanlage vorhanden' : 'Keine Klimaanlage — Klimaanlage erforderlich',
  }
}

function scoreLoadingDock(need: Need, property: Property): ScoreFactor | null {
  if (!need.requireLoadingDock) return null
  const docks = property.hardFacts?.loadingDocksCount
  if (docks === undefined) {
    return { criterion: 'loadingDock', weight: 0.05, score: 50, contribution: 2.5, explanation: 'Laderampe erforderlich — keine Daten', estimated: true }
  }
  const passed = docks > 0
  const score = passed ? 100 : 15
  return {
    criterion: 'loadingDock',
    weight: 0.05,
    score,
    contribution: score * 0.05,
    explanation: passed ? `${docks} Laderampe(n) vorhanden` : 'Keine Laderampe vorhanden — erforderlich',
  }
}

function scoreBarrierFree(need: Need, property: Property): ScoreFactor | null {
  if (!need.requireBarrierFree) return null
  const ok = property.hardFacts?.isBarrierFree
  if (ok === undefined) {
    return { criterion: 'barrierFree', weight: 0.03, score: 50, contribution: 1.5, explanation: 'Barrierefreiheit erforderlich — nicht dokumentiert', estimated: true }
  }
  const score = ok ? 100 : 20
  return {
    criterion: 'barrierFree',
    weight: 0.03,
    score,
    contribution: score * 0.03,
    explanation: ok ? 'Barrierefrei bestätigt' : 'Nicht barrierefrei — Barrierefreiheit erforderlich',
  }
}

function scoreCeilingHeight(need: Need, property: Property): ScoreFactor | null {
  const min = need.minCeilingHeightM
  if (!min || min <= 0) return null
  const actual = property.hardFacts?.ceilingHeightM
  if (actual === undefined) {
    return { criterion: 'ceilingHeight', weight: 0.04, score: 50, contribution: 2, explanation: `Mind. ${min}m Deckenhöhe erforderlich — keine Daten`, estimated: true }
  }
  const passed = actual >= min
  const score = passed ? 100 : Math.max(10, Math.round((actual / min) * 70))
  return {
    criterion: 'ceilingHeight',
    weight: 0.04,
    score,
    contribution: score * 0.04,
    explanation: passed
      ? `Deckenhöhe ${actual}m ≥ Minimum ${min}m`
      : `Deckenhöhe ${actual}m unter Minimum ${min}m`,
  }
}

function scoreMinContractDuration(need: Need, property: Property): ScoreFactor | null {
  const min = need.minContractDurationMonths
  if (!min || min <= 0) return null
  const available = property.contractDurationMonths
  if (available === undefined) {
    return { criterion: 'contractDuration', weight: 0.03, score: 50, contribution: 1.5, explanation: `Mind. ${Math.round(min / 12)} Jahre Laufzeit gewünscht — keine Daten`, estimated: true }
  }
  const passed = available >= min
  const score = passed ? 100 : Math.max(20, Math.round((available / min) * 70))
  return {
    criterion: 'contractDuration',
    weight: 0.03,
    score,
    contribution: score * 0.03,
    explanation: passed
      ? `${Math.round(available / 12)} Jahre Vertragslaufzeit — erfüllt (mind. ${Math.round(min / 12)} Jahre gewünscht)`
      : `Nur ${Math.round(available / 12)} Jahre — mind. ${Math.round(min / 12)} Jahre gewünscht`,
  }
}

const FIT_OUT_UPGRADE_COST: Record<string, number> = {
  'SHELL_BASIC': 200,
  'SHELL_FULL': 350,
  'SHELL_PREMIUM': 450,
  'BASIC_FULL': 150,
  'BASIC_PREMIUM': 250,
  'FULL_PREMIUM': 100,
}

function scoreFitOut(need: Need, property: Property): ScoreFactor | null {
  if (!need.requiredFitOut) return null
  const propFitOut = property.hardFacts?.fitOut
  const LABELS: Record<string, string> = { SHELL: 'Rohbau', BASIC: 'Basisausbau', FULL: 'Vollausbau', PREMIUM: 'Premiumausbau' }
  if (!propFitOut) {
    return { criterion: 'fitOut', weight: 0.04, score: 50, contribution: 2, explanation: `Ausbaustandard ${need.requiredFitOut} erforderlich — keine Daten`, estimated: true }
  }
  const reqLevel = FIT_OUT_LEVELS[need.requiredFitOut] ?? 1
  const propLevel = FIT_OUT_LEVELS[propFitOut] ?? 0
  if (propLevel >= reqLevel) {
    return {
      criterion: 'fitOut', weight: 0.04, score: 100, contribution: 4,
      explanation: `${LABELS[propFitOut] ?? propFitOut} erfüllt Anforderung (${LABELS[need.requiredFitOut] ?? need.requiredFitOut})`,
    }
  }
  // Check if combined budget (tenant + MAB) covers the upgrade gap
  const upgradeKey = `${propFitOut}_${need.requiredFitOut}`
  const upgradeCost = FIT_OUT_UPGRADE_COST[upgradeKey]
  const mab = property.hardFacts?.mieterausbaubeitragPerSqm ?? 0
  const tenantBudget = need.fitOutBudgetMaxPerSqm ?? 0
  const totalBudget = mab + tenantBudget
  if (upgradeCost && totalBudget >= upgradeCost) {
    const mabNote = mab > 0 ? ` (inkl. CHF ${mab} MAB)` : ''
    return {
      criterion: 'fitOut', weight: 0.04, score: 80, contribution: 3.2,
      explanation: `${LABELS[propFitOut]} — Ausbau auf ${LABELS[need.requiredFitOut]} mit CHF ${totalBudget}/m² Budget überbrückbar${mabNote}`,
    }
  }
  const score = Math.max(10, Math.round(50 - (reqLevel - propLevel) * 25))
  const budgetNote = tenantBudget > 0 || mab > 0
    ? ` (Budget CHF ${totalBudget}/m² — Ausbau ~CHF ${upgradeCost ?? '?'}/m²)`
    : ''
  return {
    criterion: 'fitOut', weight: 0.04, score, contribution: score * 0.04,
    explanation: `${LABELS[propFitOut] ?? propFitOut} — ${LABELS[need.requiredFitOut] ?? need.requiredFitOut} erforderlich${budgetNote}`,
  }
}

// ── Modifier Calculators ──────────────────────────────────────────────────────

export function calcDataQualityModifier(property: Property): number {
  const s = property.dataQuality?.score ?? 0.5
  if (s >= 0.85) return DATA_QUALITY_MODIFIER.EXCELLENT
  if (s >= 0.70) return DATA_QUALITY_MODIFIER.GOOD
  if (s >= 0.55) return DATA_QUALITY_MODIFIER.FAIR
  if (s >= 0.40) return DATA_QUALITY_MODIFIER.POOR
  return DATA_QUALITY_MODIFIER.CRITICAL
}

export function calcConfidenceModifier(property: Property): number {
  let mod = 0
  if (property.resultType === ResultType.FUTURE_AVAILABILITY) {
    mod += CONFIDENCE_MODIFIER.FUTURE_AVAILABILITY
  } else if (property.resultType === ResultType.MAISON_WORK) {
    mod += CONFIDENCE_MODIFIER.MAISON_WORK
  } else {
    // VERIFIED_PORTFOLIO
    mod += property.confidenceScore >= 0.80
      ? CONFIDENCE_MODIFIER.VERIFIED_HIGH
      : CONFIDENCE_MODIFIER.VERIFIED_MEDIUM
  }
  if (property.confidenceScore < 0.50) {
    mod += CONFIDENCE_MODIFIER.LOW_CONFIDENCE
  }
  return mod
}

// ── Main Engine Function ──────────────────────────────────────────────────────

export function calculateScore(need: Need, property: Property): MatchEngineOutput {
  const hardFilter = applyHardFilters(need, property)

  if (hardFilter.excluded) {
    return {
      propertyId: property.id,
      needId: need.id,
      excluded: true,
      excludedReason: hardFilter.reason,
      finalScore: 0,
      hardMatchScore: 0,
      softFactorScore: 0,
      dataQualityModifier: 0,
      confidenceModifier: 0,
      positiveFactors: [],
      negativeFactors: [],
      allHardFactors: [],
      allSoftFactors: [],
      tradeOffs: [],
      risks: [],
      missingData: identifyMissingData(property, need),
      nextBestActions: [],
    }
  }

  const profile = resolveProfile(need, property)

  // ── Hard criteria scoring ──────────────────────────────────────────────────
  const coreHardFactors: ScoreFactor[] = [
    scoreArea(need, property, profile.area),
    scoreLocation(need, property, profile.location),
    scoreBudget(need, property, profile.budget),
    scoreTiming(need, property, profile.timing),
  ]

  // Structured requirement scorers — optional hard factors
  const structuredFactors: ScoreFactor[] = [
    scoreGroundFloor(need, property),
    scoreParkingMin(need, property),
    scoreAirConditioning(need, property),
    scoreLoadingDock(need, property),
    scoreBarrierFree(need, property),
    scoreCeilingHeight(need, property),
    scoreMinContractDuration(need, property),
    scoreFitOut(need, property),
  ].filter((f): f is ScoreFactor => f !== null)

  const hardFactors = [...coreHardFactors, ...structuredFactors]
  const hardWeightSum = HARD_CRITERION_KEYS.reduce((s, k) => s + profile[k], 0)
    + structuredFactors.reduce((s, f) => s + f.weight, 0)
  const hardRaw = hardFactors.reduce((s, f) => s + f.contribution, 0)
  const hardMatchScore = hardWeightSum > 0 ? Math.min(100, Math.round(hardRaw / hardWeightSum)) : 0

  // ── Soft factor scoring ────────────────────────────────────────────────────
  // All 9 factors always included in output so breakdown is always complete.
  // Only weighted factors (weight > 0) contribute to the score calculation.
  const allSoftDisplay: ScoreFactor[] = SOFT_FACTOR_KEYS
    .map(k => scoreSoftFactor(k, profile[k] ?? 0, property))
  const softWeightSum = SOFT_FACTOR_KEYS.reduce((s, k) => s + (profile[k] ?? 0), 0)
  const softRaw = allSoftDisplay.filter(f => f.weight > 0).reduce((s, f) => s + f.contribution, 0)
  const softFactorScore = softWeightSum > 0 ? Math.min(100, Math.round(softRaw / softWeightSum)) : 50

  // ── Must-have criteria evaluation ─────────────────────────────────────────
  const allMustHaveText = [
    ...(need.mustCriteriaText ?? []),
    ...(need.mustHaveCriteria?.map(c => c.criterion) ?? []),
  ]
  const mustHaveEval = scoreMustHaves(allMustHaveText, property)

  // ── Final score: hard/soft weighted sum + Datenqualität/Konfidenz-Modifikatoren,
  // abzgl. severePenalty, clamped 0–100 (Trust-first: schwache Datenlage senkt den Score) ──
  const dataQualityModifier = calcDataQualityModifier(property)
  const confidenceModifier = calcConfidenceModifier(property)
  const rawFinal = hardMatchScore * 0.60 + softFactorScore * 0.40
    + dataQualityModifier + confidenceModifier - hardFilter.severePenalty
  const finalScore = Math.round(Math.min(100, Math.max(0, rawFinal)))

  // ── Factor classification — only use weighted soft factors for positive/negative ──
  const weightedSoftFactors = allSoftDisplay.filter(f => f.weight > 0)
  const allFactors = [...hardFactors, ...weightedSoftFactors]
  const THRESHOLD_POSITIVE = 70
  const THRESHOLD_NEGATIVE = 45
  const positiveFactors = allFactors
    .filter(f => f.score >= THRESHOLD_POSITIVE)
    .sort((a, b) => b.contribution - a.contribution)
    .slice(0, 4)
  const negativeFactors = allFactors
    .filter(f => f.score < THRESHOLD_NEGATIVE)
    .sort((a, b) => a.contribution - b.contribution)
    .slice(0, 4)

  const tradeOffs = analyzeTradeOffs(hardFactors, weightedSoftFactors, need, property)
  const risks = analyzeRisks(property, hardFactors)
  const missingData = identifyMissingData(property, need)

  const output: MatchEngineOutput = {
    propertyId: property.id,
    needId: need.id,
    excluded: false,
    finalScore,
    hardMatchScore,
    softFactorScore,
    dataQualityModifier,
    confidenceModifier,
    positiveFactors,
    negativeFactors,
    allHardFactors: hardFactors,
    allSoftFactors: allSoftDisplay,
    tradeOffs,
    risks,
    missingData,
    mustHaveEvaluation: mustHaveEval.results.length > 0 ? mustHaveEval.results : undefined,
    nextBestActions: [],  // filled by rankingEngine
  }

  output.nextBestActions = generateNextBestActions(output, property, need)
  return output
}
