/**
 * Tests permanentes de validación de gateways.
 * Protegen las reglas de negocio: XOR debe sumar 1, AND no tiene probs editables.
 */

import { describe, it, expect } from 'vitest'
import { isXorSumValid } from '@/features/workspace/GatewayPanel'
import { runSimulation } from '@/domain/simulation'
import type { GatewayConfig, SimulationInput, Activity } from '@/domain/types'
import { v4 as uuidv4 } from 'uuid'
import { readFileSync } from 'fs'
import { resolve } from 'path'

function makeBranches(probs: number[]): GatewayConfig['branches'] {
  return probs.map((p, i) => ({ flowId: `f${i}`, targetElementId: `t${i}`, probability: p }))
}

function makeXorGateway(probs: number[]): GatewayConfig {
  return {
    id: uuidv4(), processId: 'p', bpmnElementId: 'gw1',
    gatewayType: 'exclusive',
    branches: makeBranches(probs),
  }
}

function makeParallelGateway(n: number): GatewayConfig {
  return {
    id: uuidv4(), processId: 'p', bpmnElementId: 'gw1',
    gatewayType: 'parallel',
    branches: makeBranches(Array(n).fill(1.0)),
  }
}

// ─── isXorSumValid ────────────────────────────────────────────────────────────
describe('isXorSumValid', () => {
  it('válido: suma exactamente 1.0', () => {
    expect(isXorSumValid(makeXorGateway([0.6, 0.4]))).toBe(true)
  })

  it('válido: suma = 1.0 con 3 ramas (0.5 + 0.3 + 0.2)', () => {
    expect(isXorSumValid(makeXorGateway([0.5, 0.3, 0.2]))).toBe(true)
  })

  it('válido: tolerancia de 0.5% — 0.999 se acepta como 1.0', () => {
    expect(isXorSumValid(makeXorGateway([0.499, 0.501]))).toBe(true)
  })

  it('inválido: suma = 0.5 (faltan 50%)', () => {
    expect(isXorSumValid(makeXorGateway([0.3, 0.2]))).toBe(false)
  })

  it('inválido: suma = 1.5 (excede 100%)', () => {
    expect(isXorSumValid(makeXorGateway([0.8, 0.7]))).toBe(false)
  })

  it('inválido: suma = 0 (gateways sin configurar)', () => {
    expect(isXorSumValid(makeXorGateway([0, 0]))).toBe(false)
  })

  it('gateway paralelo siempre válido (no aplica regla de suma)', () => {
    expect(isXorSumValid(makeParallelGateway(2))).toBe(true)
    expect(isXorSumValid(makeParallelGateway(3))).toBe(true)
  })

  it('gateway inclusivo tratado como exclusivo — aplica regla de suma', () => {
    const gw: GatewayConfig = { ...makeXorGateway([0.6, 0.4]), gatewayType: 'inclusive' }
    // inclusive usa isXorSumValid solo para el display; el motor lo trata igual
    // Aquí verificamos que la función no lanza para gateways inclusive
    expect(() => isXorSumValid(gw)).not.toThrow()
  })
})

// ─── Motor: gateways con distribución 70/30 sobre medium BPMN ────────────────
describe('motor / propagación con GatewayConfig correcta', () => {
  const xml = readFileSync(resolve(__dirname, '../../public/sample-processes/medium-with-gateways.bpmn'), 'utf-8')

  function buildSimInput(datosProbs: [number, number], decisionProbs: [number, number]): SimulationInput {
    const processId = 'gv-test'

    const actIds = ['task_recv', 'task_valid', 'task_pedir_docs', 'task_score',
      'task_bureau', 'task_empleador', 'task_analisis',
      'task_aprobar', 'task_rechazar', 'task_desembolso', 'task_archivo']

    const activities = new Map<string, Activity>(
      actIds.map((id) => [id, {
        id: uuidv4(), processId, bpmnElementId: id, name: id,
        type: 'task', directCostFixed: 100, executionTimeMinutes: 30, assignedResources: [],
        automatable: false, automatedCostFixed: 0, automatedTimeMinutes: 0, automatedAssignedResources: [],
      }])
    )

    const gateways = new Map<string, GatewayConfig>([
      ['gw_datos', {
        id: uuidv4(), processId, bpmnElementId: 'gw_datos', gatewayType: 'exclusive',
        branches: [
          { flowId: 'flow_gw1_completo', targetElementId: 'task_score', probability: datosProbs[0] },
          { flowId: 'flow_gw1_incompleto', targetElementId: 'task_pedir_docs', probability: datosProbs[1] },
        ],
      }],
      ['gw_parallel_split', {
        id: uuidv4(), processId, bpmnElementId: 'gw_parallel_split', gatewayType: 'parallel',
        branches: [
          { flowId: 'flow_para_bureau', targetElementId: 'task_bureau', probability: 1.0 },
          { flowId: 'flow_para_empleador', targetElementId: 'task_empleador', probability: 1.0 },
        ],
      }],
      ['gw_decision', {
        id: uuidv4(), processId, bpmnElementId: 'gw_decision', gatewayType: 'exclusive',
        branches: [
          { flowId: 'flow_dec_aprobado', targetElementId: 'task_aprobar', probability: decisionProbs[0] },
          { flowId: 'flow_dec_rechazado', targetElementId: 'task_rechazar', probability: decisionProbs[1] },
        ],
      }],
    ])

    return { processXml: xml, activities, gateways, resources: new Map(), globalSettings: { currency: 'USD', overheadPercentage: 0 } }
  }

  it('rama "completo" 70% → task_score tiene prob ≈ 0.7', () => {
    const result = runSimulation(buildSimInput([0.7, 0.3], [0.8, 0.2]))
    const score = result.perActivity.find((a) => a.bpmnElementId === 'task_score')
    expect(score?.executionProbability).toBeCloseTo(0.7, 2)
  })

  it('rama "incompleto" 30% → task_pedir_docs tiene prob ≈ 0.3', () => {
    const result = runSimulation(buildSimInput([0.7, 0.3], [0.8, 0.2]))
    const pedir = result.perActivity.find((a) => a.bpmnElementId === 'task_pedir_docs')
    expect(pedir?.executionProbability).toBeCloseTo(0.3, 2)
  })

  it('task_aprobar tiene prob = 0.8 (task_analisis converge ambos caminos a 1.0, luego 80% decision)', () => {
    // task_analisis recibe:
    //   - camino completo (70%): AND join correcto → 0.7
    //   - camino incompleto (30%): task_pedir_docs → 0.3
    //   - suma XOR: 0.7 + 0.3 = 1.0
    // gw_decision → task_aprobar: 1.0 × 0.8 = 0.8
    const result = runSimulation(buildSimInput([0.7, 0.3], [0.8, 0.2]))
    const analisis = result.perActivity.find((a) => a.bpmnElementId === 'task_analisis')
    const aprobar = result.perActivity.find((a) => a.bpmnElementId === 'task_aprobar')
    const rechazar = result.perActivity.find((a) => a.bpmnElementId === 'task_rechazar')
    expect(analisis?.executionProbability).toBeCloseTo(1.0, 2)
    expect(aprobar?.executionProbability).toBeCloseTo(0.8, 2)
    expect(rechazar?.executionProbability).toBeCloseTo(0.2, 2)
  })

  it('gateway AND: bureau y empleador tienen la misma prob que score', () => {
    const result = runSimulation(buildSimInput([1.0, 0.0], [0.5, 0.5]))
    const bureau = result.perActivity.find((a) => a.bpmnElementId === 'task_bureau')
    const empleador = result.perActivity.find((a) => a.bpmnElementId === 'task_empleador')
    // Ambos deben tener la misma probabilidad (AND paralelo)
    expect(bureau?.executionProbability).toBeCloseTo(empleador?.executionProbability ?? -1, 2)
  })

  it('con probs que no suman 1 el motor no explota, solo produce resultados parciales', () => {
    // Esto valida que el motor es robusto aunque la UI deba prevenirlo
    expect(() => runSimulation(buildSimInput([0.3, 0.3], [0.5, 0.5]))).not.toThrow()
  })
})
