import type { FraudDb } from './types.js'
import type {
  FraudAction,
  FraudAdapter,
  FraudSignal,
  AdapterConfig,
  DecisionPoint,
  PipelineResult,
} from './types.js'
import { insertFraudEvent } from './fraud-events.js'

const ACTION_SEVERITY: Record<string, number> = {
  pass: 0,
  flag: 1,
  hold: 2,
  block: 3,
}

/** Resolve MAX-severity across all signals. */
export function resolveAction(signals: FraudSignal[]): FraudAction {
  let max = 0
  let result: FraudAction = 'pass'
  for (const s of signals) {
    const sev = ACTION_SEVERITY[s.action] ?? 0
    if (sev > max) {
      max = sev
      result = s.action
    }
  }
  return result
}

/** Downgrade signal action to at most cfg.action if specified. */
export function applyDowngrade(signal: FraudSignal, cfg: AdapterConfig): FraudSignal {
  if (!cfg.action) return signal
  const natural = ACTION_SEVERITY[signal.action] ?? 0
  const cap = ACTION_SEVERITY[cfg.action] ?? 3
  if (natural <= cap) return signal
  return { ...signal, action: cfg.action }
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const ADAPTERS: FraudAdapter<any>[] = []

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function registerAdapter(adapter: FraudAdapter<any>): void {
  ADAPTERS.push(adapter)
}

/** Test-only: clear the in-memory adapter registry between cases. */
export function resetAdaptersForTest(): void {
  ADAPTERS.length = 0
}

export function getAdaptersForPoint(point: DecisionPoint): FraudAdapter<unknown>[] {
  return ADAPTERS.filter((a) => a.points.includes(point))
}

export interface RunPipelineInput<Ctx> {
  point: DecisionPoint
  ctx: Ctx
  userId: string
  referralId?: string
  fraudConfig: Record<string, AdapterConfig>
  db: FraudDb
  /** If false, skip fraud_events insert (e.g. CLICK → AE-only). */
  persistEvents?: boolean
}

export async function runFraudPipeline<Ctx>(input: RunPipelineInput<Ctx>): Promise<PipelineResult> {
  const adapters = getAdaptersForPoint(input.point)
  const enabledAdapters = adapters.filter(
    (a) => (input.fraudConfig[a.key] ?? { enabled: true }).enabled !== false,
  )

  // EARN fail-closed: zero EVALUATING adapters (none registered OR all config-disabled) must hold —
  // never silent-pass unscreened commission.
  if (input.point === 'EARN' && enabledAdapters.length === 0) {
    return { action: 'hold', signals: [] }
  }

  const signals: FraudSignal[] = []

  await Promise.all(
    enabledAdapters.map(async (adapter) => {
      const cfg: AdapterConfig = input.fraudConfig[adapter.key] ?? { enabled: true }
      try {
        const raw = await adapter.evaluate(input.ctx, cfg)
        if (!raw || raw.action === 'pass') return
        const signal = applyDowngrade(raw, cfg)
        signals.push(signal)
        if (input.persistEvents !== false) {
          await insertFraudEvent(input.db, {
            userId: input.userId,
            referralId: input.referralId,
            decisionPoint: input.point,
            signal,
          })
        }
      } catch (err) {
        console.error(`[fraud-pipeline] adapter=${adapter.key} point=${input.point} error`, err)
        // EARN only: fail-closed — adapter failure must not auto-release commission.
        if (input.point !== 'EARN') return
        const signal: FraudSignal = {
          adapter: adapter.key,
          action: 'hold',
          codes: ['adapter_error'],
        }
        signals.push(signal)
        if (input.persistEvents !== false) {
          await insertFraudEvent(input.db, {
            userId: input.userId,
            referralId: input.referralId,
            decisionPoint: input.point,
            signal,
          })
        }
      }
    }),
  )

  return { action: resolveAction(signals), signals }
}
