/**
 * Test integrador del flujo completo del Sprint 1.
 *
 * Verifica que las piezas del dominio conectan correctamente:
 *   parseBpmnXml → extractActivityElements → runSimulation (actual + automated)
 *   → calculateRoi → KPIs correctos
 *
 * BPMN base: medium-with-gateways (Aprobación de crédito)
 * — 11 actividades, 3 gateways (2 XOR + 1 AND), 5 flujos condicionales
 *
 * Costos usados (deliberadamente simples para assertions deterministas):
 *   - Todas las actividades: directCostFixed=$200, executionTimeMinutes=60
 *   - Automatable: solo task_recv y task_score (las más caras por prob alta)
 *     - automatedCostFixed=$20 (90% de ahorro), automatedTimeMinutes=5
 *   - annualFrequency=1200 (100 ejecuciones/mes), horizonte=3 años, inversión=$50.000
 */

import { describe, it, expect } from 'vitest'
import { readFileSync } from 'fs'
import { resolve } from 'path'
import { v4 as uuidv4 } from 'uuid'
import { parseBpmnXml, extractActivityElements, extractGatewayElements } from '@/domain/bpmn-parser'
import { runSimulation } from '@/domain/simulation'
import { calculateRoi } from '@/domain/roi'
import { bpmnTypeToGatewayType } from '@/domain/types'
import type { Activity, GatewayConfig, SimulationInput } from '@/domain/types'

const MEDIUM_XML = readFileSync(
  resolve(__dirname, '../../public/sample-processes/medium-with-gateways.bpmn'),
  'utf-8'
)

// Actividades automatable y sus costos automatizados
const AUTOMATABLE_IDS = new Set(['task_recv', 'task_score'])
const AUTOMATED_COST = 20
const AUTOMATED_TIME = 5

function buildActivities(xml: string): Map<string, Activity> {
  const graph = parseBpmnXml(xml)
  const elements = extractActivityElements(graph)
  return new Map(
    elements.map((el) => [el.bpmnElementId, {
      id: uuidv4(),
      processId: 'sprint1-test',
      bpmnElementId: el.bpmnElementId,
      name: el.name,
      type: el.type,
      directCostFixed: 200,
      executionTimeMinutes: 60,
      assignedResources: [],
      automatable: AUTOMATABLE_IDS.has(el.bpmnElementId),
      automatedCostFixed: AUTOMATABLE_IDS.has(el.bpmnElementId) ? AUTOMATED_COST : 0,
      automatedTimeMinutes: AUTOMATABLE_IDS.has(el.bpmnElementId) ? AUTOMATED_TIME : 0,
      automatedAssignedResources: [],
    }])
  )
}

function buildGateways(xml: string): Map<string, GatewayConfig> {
  const graph = parseBpmnXml(xml)
  const elements = extractGatewayElements(graph)
  return new Map(
    elements.map((el) => {
      const gwType = bpmnTypeToGatewayType(el.gatewayType as any)
      const n = el.outgoing.length
      return [el.bpmnElementId, {
        id: uuidv4(),
        processId: 'sprint1-test',
        bpmnElementId: el.bpmnElementId,
        gatewayType: gwType,
        branches: el.outgoing.map((flowId) => ({
          flowId,
          targetElementId: graph.flows.get(flowId)?.targetRef ?? '',
          probability: gwType === 'parallel' ? 1.0 : parseFloat((1 / n).toFixed(4)),
        })),
      }]
    })
  )
}

const BASE_INPUT: Omit<SimulationInput, 'scenario'> = {
  processXml: MEDIUM_XML,
  activities: buildActivities(MEDIUM_XML),
  gateways: buildGateways(MEDIUM_XML),
  resources: new Map(),
  globalSettings: { currency: 'USD', overheadPercentage: 0.2 },
}

const VOLUMETRY = {
  annualFrequency: 1200,
  analysisHorizonYears: 3,
  automationInvestment: 50_000,
}

// ─── Tests ────────────────────────────────────────────────────────────────────

describe('Sprint 1 — flujo integrado: simulación dual + ROI', () => {
  const resultActual = runSimulation({ ...BASE_INPUT, scenario: 'actual' })
  const resultAutomated = runSimulation({ ...BASE_INPUT, scenario: 'automated' })

  // ─── Simulación actual ──────────────────────────────────────────────────

  it('simulación actual produce resultado no nulo con actividades reales', () => {
    expect(resultActual.totalCost).toBeGreaterThan(0)
    expect(resultActual.perActivity.length).toBeGreaterThan(0)
    expect(resultActual.warnings).toBeDefined()
  })

  it('simulación actual: task_recv tiene probabilidad 1.0 (primer nodo del proceso)', () => {
    const recv = resultActual.perActivity.find((a) => a.bpmnElementId === 'task_recv')
    expect(recv).toBeDefined()
    expect(recv!.executionProbability).toBeCloseTo(1.0)
  })

  it('simulación actual: las actividades con costos tienen expectedDirectCost > 0', () => {
    for (const act of resultActual.perActivity) {
      expect(Number.isNaN(act.expectedDirectCost)).toBe(false)
      expect(act.expectedDirectCost).toBeGreaterThanOrEqual(0)
    }
  })

  // ─── Simulación automatizada ────────────────────────────────────────────

  it('simulación automatizada produce totalCost menor que actual', () => {
    // Hay actividades automatable con costo menor → total debe bajar
    expect(resultAutomated.totalCost).toBeLessThan(resultActual.totalCost)
  })

  it('probabilidades de ejecución son idénticas entre escenarios', () => {
    for (const actActual of resultActual.perActivity) {
      const actAuto = resultAutomated.perActivity.find(
        (a) => a.bpmnElementId === actActual.bpmnElementId
      )
      expect(actAuto).toBeDefined()
      expect(actAuto!.executionProbability).toBeCloseTo(actActual.executionProbability, 5)
    }
  })

  it('actividades automatable tienen expectedDirectCost menor en escenario automatizado', () => {
    for (const bpmnElementId of AUTOMATABLE_IDS) {
      const actual = resultActual.perActivity.find((a) => a.bpmnElementId === bpmnElementId)
      const automated = resultAutomated.perActivity.find((a) => a.bpmnElementId === bpmnElementId)
      if (!actual || !automated) continue  // podría no aparecer si prob=0
      expect(automated.expectedDirectCost).toBeLessThan(actual.expectedDirectCost)
    }
  })

  it('actividades no automatable tienen el mismo costo en ambos escenarios', () => {
    for (const act of resultActual.perActivity) {
      if (AUTOMATABLE_IDS.has(act.bpmnElementId)) continue
      const auto = resultAutomated.perActivity.find((a) => a.bpmnElementId === act.bpmnElementId)!
      expect(auto.expectedDirectCost).toBeCloseTo(act.expectedDirectCost, 5)
    }
  })

  it('ningún campo numérico del resultado automatizado es NaN', () => {
    expect(Number.isNaN(resultAutomated.totalCost)).toBe(false)
    expect(Number.isNaN(resultAutomated.totalDirectCost)).toBe(false)
    expect(Number.isNaN(resultAutomated.totalIndirectCost)).toBe(false)
    for (const act of resultAutomated.perActivity) {
      expect(Number.isNaN(act.expectedDirectCost)).toBe(false)
      expect(Number.isNaN(act.expectedTotalCost)).toBe(false)
      expect(Number.isNaN(act.percentOfTotal)).toBe(false)
    }
  })

  // ─── calculateRoi conecta con los resultados de simulación ─────────────

  it('calculateRoi recibe costos de ambas simulaciones y devuelve KPIs válidos', () => {
    const roi = calculateRoi({
      costPerExecutionActual: resultActual.totalCost,
      costPerExecutionAutomated: resultAutomated.totalCost,
      ...VOLUMETRY,
    })

    // Ahorro positivo (automated < actual)
    expect(roi.savingsPerExecution).toBeGreaterThan(0)
    expect(roi.annualSavings).toBeGreaterThan(0)

    // Payback finito y positivo
    expect(roi.paybackMonths).toBeGreaterThan(0)
    expect(Number.isFinite(roi.paybackMonths)).toBe(true)

    // Con inversión $50.000 y ahorro anual razonable, debería pagar dentro del horizonte de 3 años
    // (esto depende de los costos calculados, pero verificamos que es un número razonable)
    expect(roi.paybackYears).toBeGreaterThan(0)

    // ROI anualizado positivo (ahorro > 0, inversión > 0)
    expect(roi.roiAnnualPercent).toBeGreaterThan(0)
    expect(Number.isNaN(roi.roiAnnualPercent)).toBe(false)
    expect(Number.isNaN(roi.cumulativeSavingsHorizon)).toBe(false)
  })

  it('ahorro acumulado a 3 años supera la inversión si hay ahorro positivo suficiente', () => {
    const roi = calculateRoi({
      costPerExecutionActual: resultActual.totalCost,
      costPerExecutionAutomated: resultAutomated.totalCost,
      ...VOLUMETRY,
    })

    if (roi.annualSavings * VOLUMETRY.analysisHorizonYears > VOLUMETRY.automationInvestment) {
      expect(roi.cumulativeSavingsHorizon).toBeGreaterThan(0)
      expect(roi.breaksEvenInHorizon).toBe(true)
    } else {
      // Si el ahorro es menor que la inversión en 3 años, el test es informativo
      expect(roi.cumulativeSavingsHorizon).toBeDefined()
    }
  })

  // ─── Transparencia metodológica: actividades automatable con costo cero ─

  it('detecta actividades automatable con automatedCostFixed=0 para transparencia', () => {
    // En nuestro fixture solo task_recv y task_score son automatable con costo > 0.
    // Si alguien marca una actividad como automatable pero no pone costo,
    // el resultado automatizado de esa actividad debe tener cost=0.
    const activities = buildActivities(MEDIUM_XML)

    // Forzar una actividad automatable con costo cero (simula campo no configurado)
    const taskValid = activities.get('task_valid')
    if (taskValid) {
      activities.set('task_valid', { ...taskValid, automatable: true, automatedCostFixed: 0, automatedTimeMinutes: 0 })
    }

    const result = runSimulation({ ...BASE_INPUT, activities, scenario: 'automated' })
    const validResult = result.perActivity.find((a) => a.bpmnElementId === 'task_valid')

    if (validResult && validResult.executionProbability > 0) {
      // automatable=true pero costos en 0 → contribuye con $0 al total automatizado
      expect(validResult.expectedDirectCost).toBe(0)
      expect(Number.isNaN(validResult.expectedDirectCost)).toBe(false)
    }
  })
})
