/**
 * Tests permanentes del parser BPMN.
 * Protegen contra regresiones cuando se modifique parseBpmnXml,
 * extractActivityElements o extractGatewayElements.
 *
 * Conteos esperados (fijos, derivados del XML de los sample BPMNs):
 *
 *   simple-linear.bpmn
 *     nodes total : 7  (1 startEvent + 5 tasks + 1 endEvent)
 *     flows total : 6
 *     tasks       : 5
 *     gateways    : 0
 *     events      : 2
 *     divergentes : 0
 *
 *   medium-with-gateways.bpmn
 *     nodes total : 17  (1 start + 11 tasks + 4 gateways + 1 end)
 *     flows total : 19
 *     tasks       : 11
 *     gateways    : 4  (gw_datos XOR, gw_parallel_split AND, gw_parallel_join AND, gw_decision XOR)
 *     events      : 2
 *     divergentes : 3  (gw_datos:2, gw_parallel_split:2, gw_decision:2)
 *
 *   complex-with-loop.bpmn
 *     nodes total : 13  (1 start + 9 tasks + 2 gateways + 1 end)
 *     flows total : 14
 *     tasks       : 9
 *     gateways    : 2  (gw_resultado XOR 3-salidas, gw_merge XOR convergente)
 *     events      : 2
 *     divergentes : 1  (gw_resultado:3)
 */

import { describe, it, expect } from 'vitest'
import { readFileSync } from 'fs'
import { resolve } from 'path'
import {
  parseBpmnXml,
  extractActivityElements,
  extractGatewayElements,
  BpmnParseError,
} from '@/domain/bpmn-parser'
import { isTaskNode, isGatewayNode } from '@/domain/types'

function loadSample(name: string): string {
  return readFileSync(resolve(__dirname, '../../public/sample-processes', name), 'utf-8')
}

function countByCategory(graph: ReturnType<typeof parseBpmnXml>) {
  let tasks = 0, gateways = 0, events = 0, unknown = 0
  for (const node of graph.nodes.values()) {
    if (isTaskNode(node.type)) tasks++
    else if (isGatewayNode(node.type)) gateways++
    else if (['startEvent', 'endEvent', 'intermediateThrowEvent', 'intermediateCatchEvent'].includes(node.type)) events++
    else unknown++
  }
  return { tasks, gateways, events, unknown }
}

// ─── simple-linear.bpmn ──────────────────────────────────────────────────────
describe('parser / simple-linear.bpmn', () => {
  const xml = loadSample('simple-linear.bpmn')

  it('parsea sin lanzar error', () => {
    expect(() => parseBpmnXml(xml)).not.toThrow()
  })

  it('grafo: 7 nodos y 6 flujos de secuencia', () => {
    const g = parseBpmnXml(xml)
    expect(g.nodes.size).toBe(7)
    expect(g.flows.size).toBe(6)
  })

  it('grafo: 5 tasks | 0 gateways | 2 eventos', () => {
    const g = parseBpmnXml(xml)
    const { tasks, gateways, events, unknown } = countByCategory(g)
    expect(tasks).toBe(5)
    expect(gateways).toBe(0)
    expect(events).toBe(2)
    expect(unknown).toBe(0)
  })

  it('1 startEvent detectado como punto de entrada', () => {
    const g = parseBpmnXml(xml)
    expect(g.startEventIds).toHaveLength(1)
    expect(g.startEventIds[0]).toBe('start')
  })

  it('extractActivityElements: 5 actividades con nombres correctos', () => {
    const g = parseBpmnXml(xml)
    const acts = extractActivityElements(g)
    expect(acts).toHaveLength(5)
    const names = acts.map((a) => a.name)
    expect(names).toContain('Recibir solicitud')
    expect(names).toContain('Verificar documentación')
    expect(names).toContain('Procesar pedido')
    expect(names).toContain('Notificar al cliente')
    expect(names).toContain('Archivar expediente')
    acts.forEach((a) => expect(a.type).toBe('task'))
  })

  it('extractGatewayElements: 0 gateways divergentes (proceso lineal)', () => {
    const g = parseBpmnXml(xml)
    const gws = extractGatewayElements(g)
    expect(gws).toHaveLength(0)
  })
})

// ─── medium-with-gateways.bpmn ───────────────────────────────────────────────
describe('parser / medium-with-gateways.bpmn', () => {
  const xml = loadSample('medium-with-gateways.bpmn')

  it('parsea sin lanzar error', () => {
    expect(() => parseBpmnXml(xml)).not.toThrow()
  })

  it('grafo: 17 nodos y 19 flujos de secuencia', () => {
    const g = parseBpmnXml(xml)
    expect(g.nodes.size).toBe(17)
    expect(g.flows.size).toBe(19)
  })

  it('grafo: 11 tasks | 4 gateways | 2 eventos', () => {
    const g = parseBpmnXml(xml)
    const { tasks, gateways, events, unknown } = countByCategory(g)
    expect(tasks).toBe(11)
    expect(gateways).toBe(4)
    expect(events).toBe(2)
    expect(unknown).toBe(0)
  })

  it('extractActivityElements: exactamente 11 actividades', () => {
    const g = parseBpmnXml(xml)
    const acts = extractActivityElements(g)
    expect(acts).toHaveLength(11)
    const ids = acts.map((a) => a.bpmnElementId)
    expect(ids).toContain('task_recv')
    expect(ids).toContain('task_valid')
    expect(ids).toContain('task_pedir_docs')
    expect(ids).toContain('task_score')
    expect(ids).toContain('task_bureau')
    expect(ids).toContain('task_empleador')
    expect(ids).toContain('task_analisis')
    expect(ids).toContain('task_aprobar')
    expect(ids).toContain('task_rechazar')
    expect(ids).toContain('task_desembolso')
    expect(ids).toContain('task_archivo')
  })

  it('extractGatewayElements: 3 gateways divergentes con salidas correctas', () => {
    const g = parseBpmnXml(xml)
    const gws = extractGatewayElements(g)
    expect(gws).toHaveLength(3)

    const byId = Object.fromEntries(gws.map((gw) => [gw.bpmnElementId, gw]))

    // gw_datos: XOR con 2 salidas
    expect(byId['gw_datos']).toBeDefined()
    expect(byId['gw_datos'].outgoing).toHaveLength(2)
    expect(byId['gw_datos'].gatewayType).toBe('exclusiveGateway')

    // gw_parallel_split: AND con 2 salidas
    expect(byId['gw_parallel_split']).toBeDefined()
    expect(byId['gw_parallel_split'].outgoing).toHaveLength(2)
    expect(byId['gw_parallel_split'].gatewayType).toBe('parallelGateway')

    // gw_decision: XOR con 2 salidas
    expect(byId['gw_decision']).toBeDefined()
    expect(byId['gw_decision'].outgoing).toHaveLength(2)
    expect(byId['gw_decision'].gatewayType).toBe('exclusiveGateway')

    // gw_parallel_join NO debe estar (convergente: 1 salida)
    expect(byId['gw_parallel_join']).toBeUndefined()
  })
})

// ─── complex-with-loop.bpmn ───────────────────────────────────────────────────
describe('parser / complex-with-loop.bpmn', () => {
  const xml = loadSample('complex-with-loop.bpmn')

  it('parsea sin lanzar error', () => {
    expect(() => parseBpmnXml(xml)).not.toThrow()
  })

  it('grafo: 13 nodos y 14 flujos de secuencia', () => {
    const g = parseBpmnXml(xml)
    expect(g.nodes.size).toBe(13)
    expect(g.flows.size).toBe(14)
  })

  it('grafo: 9 tasks | 2 gateways | 2 eventos', () => {
    const g = parseBpmnXml(xml)
    const { tasks, gateways, events, unknown } = countByCategory(g)
    expect(tasks).toBe(9)
    expect(gateways).toBe(2)
    expect(events).toBe(2)
    expect(unknown).toBe(0)
  })

  it('extractActivityElements: exactamente 9 actividades', () => {
    const g = parseBpmnXml(xml)
    const acts = extractActivityElements(g)
    expect(acts).toHaveLength(9)
    const ids = acts.map((a) => a.bpmnElementId)
    expect(ids).toContain('task_preparar')
    expect(ids).toContain('task_inspeccion')
    expect(ids).toContain('task_documentar')
    expect(ids).toContain('task_correcciones')
    expect(ids).toContain('task_rechazo')
    expect(ids).toContain('task_informe_rechazo')
    expect(ids).toContain('task_entrega')
    expect(ids).toContain('task_certificado')
    expect(ids).toContain('task_registro')
  })

  it('extractGatewayElements: 1 gateway divergente (gw_resultado con 3 salidas)', () => {
    const g = parseBpmnXml(xml)
    const gws = extractGatewayElements(g)
    expect(gws).toHaveLength(1)
    expect(gws[0].bpmnElementId).toBe('gw_resultado')
    expect(gws[0].outgoing).toHaveLength(3)
    expect(gws[0].gatewayType).toBe('exclusiveGateway')
    // gw_merge es convergente (1 salida) — no debe aparecer
  })

  it('el flujo de loop existe en el grafo (correc_inspeccion apunta a task_inspeccion)', () => {
    const g = parseBpmnXml(xml)
    const loopFlow = g.flows.get('flow_correc_inspeccion')
    expect(loopFlow).toBeDefined()
    expect(loopFlow!.sourceRef).toBe('task_correcciones')
    expect(loopFlow!.targetRef).toBe('task_inspeccion')
  })
})

// ─── Casos de error del parser ────────────────────────────────────────────────
describe('parser / manejo de errores', () => {
  it('lanza BpmnParseError si el XML no tiene elemento <process>', () => {
    expect(() => parseBpmnXml('<definitions><notAProcess/></definitions>')).toThrow(BpmnParseError)
  })

  it('lanza BpmnParseError si el XML está malformado', () => {
    expect(() => parseBpmnXml('<definitions><process id="p"><unclosed')).toThrow(BpmnParseError)
  })

  it('lanza BpmnParseError si el proceso no tiene startEvent', () => {
    const xml = `<?xml version="1.0"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL">
  <process id="p"><task id="t1" name="Solo tarea"/></process>
</definitions>`
    expect(() => parseBpmnXml(xml)).toThrow(BpmnParseError)
  })
})
