/**
 * Tests de renderizado DOM de los componentes del reporte.
 * bpmn-js y echarts-for-react se mockean (requieren browser/canvas reales).
 * useReportData se mockea para inyectar datos controlados.
 */
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, screen, fireEvent } from '@testing-library/react'
import React from 'react'

// ─── Mocks de dependencias del browser ───────────────────────────────────────

vi.mock('bpmn-js/lib/Viewer', () => ({
  default: class MockViewer {
    importXML = vi.fn().mockResolvedValue({ warnings: [] })
    get = vi.fn().mockImplementation((svc: string) => {
      if (svc === 'canvas') return { zoom: vi.fn(), addMarker: vi.fn(), removeMarker: vi.fn() }
      if (svc === 'elementRegistry') return { forEach: vi.fn(), getGraphics: vi.fn().mockReturnValue(null) }
      if (svc === 'eventBus') return { on: vi.fn(), off: vi.fn() }
      return {}
    })
    destroy = vi.fn()
  },
}))

vi.mock('echarts-for-react', () => ({
  default: ({ style }: { option: unknown; style?: React.CSSProperties }) => (
    <div data-testid="echarts-chart" style={style} />
  ),
}))

// ─── Fixtures ─────────────────────────────────────────────────────────────────

import type { ActivitySimResult, SimulationResult } from '@/domain/types'

const mockActivities: ActivitySimResult[] = [
  {
    activityId: 'a1', bpmnElementId: 'task1', activityName: 'Recibir solicitud',
    expectedDirectCost: 500, expectedIndirectCost: 100, expectedTotalCost: 600,
    percentOfTotal: 60, executionProbability: 1.0, expectedExecutions: 1.0,
    executionTimeMinutes: 30, resourceCostBreakdown: [],
  },
  {
    activityId: 'a2', bpmnElementId: 'task2', activityName: 'Procesar pedido',
    expectedDirectCost: 250, expectedIndirectCost: 50, expectedTotalCost: 300,
    percentOfTotal: 30, executionProbability: 1.0, expectedExecutions: 1.0,
    executionTimeMinutes: 60, resourceCostBreakdown: [],
  },
  {
    activityId: 'a3', bpmnElementId: 'task3', activityName: 'Notificar cliente',
    expectedDirectCost: 83, expectedIndirectCost: 17, expectedTotalCost: 100,
    percentOfTotal: 10, executionProbability: 1.0, expectedExecutions: 1.0,
    executionTimeMinutes: 15, resourceCostBreakdown: [],
  },
]

const mockResult: SimulationResult = {
  totalCost: 1000,
  totalDirectCost: 833,
  totalIndirectCost: 167,
  totalTimeMinutes: 105,
  perActivity: mockActivities,
  perResource: [],
  warnings: [],
}

// ─── KpiBar ───────────────────────────────────────────────────────────────────

import { KpiBar } from '@/features/report/KpiBar'

describe('KpiBar', () => {
  it('renderiza exactamente 4 KPI cards', () => {
    render(<KpiBar result={mockResult} currency="USD" />)
    const cards = screen.getAllByTestId('kpi-card')
    expect(cards).toHaveLength(4)
  })

  it('la primera card muestra el costo total formateado', () => {
    render(<KpiBar result={mockResult} currency="USD" />)
    // El valor $1,000.00 debe aparecer en algún lugar del DOM
    const cards = screen.getAllByTestId('kpi-card')
    const allText = cards.map((c) => c.textContent).join(' ')
    expect(allText).toMatch(/1[.,]000/)
  })

  it('con currency PYG: muestra el valor sin crash', () => {
    expect(() => render(<KpiBar result={mockResult} currency="PYG" />)).not.toThrow()
    const cards = screen.getAllByTestId('kpi-card')
    expect(cards).toHaveLength(4)
  })

  it('tiempo total aparece formateado en minutos/horas', () => {
    render(<KpiBar result={mockResult} currency="USD" />)
    // 105 min = 1h 45min
    const text = document.body.textContent ?? ''
    expect(text).toMatch(/1h|105/)
  })

  it('cantidad de actividades es visible', () => {
    render(<KpiBar result={mockResult} currency="USD" />)
    const text = document.body.textContent ?? ''
    expect(text).toMatch(/3/)
  })
})

// ─── WarningsBanner ───────────────────────────────────────────────────────────

import { WarningsBanner } from '@/features/report/WarningsBanner'

describe('WarningsBanner', () => {
  it('no renderiza nada cuando no hay warnings', () => {
    const { container } = render(<WarningsBanner warnings={[]} />)
    expect(container.firstChild).toBeNull()
  })

  it('renderiza el banner cuando hay warnings', () => {
    render(<WarningsBanner warnings={['Loop detectado en task1']} />)
    expect(screen.getByText(/Loop detectado/)).toBeInTheDocument()
  })

  it('con múltiples warnings: muestra todos', () => {
    render(<WarningsBanner warnings={['Warning 1', 'Warning 2', 'Warning 3']} />)
    expect(screen.getByText(/Warning 1/)).toBeInTheDocument()
    expect(screen.getByText(/Warning 2/)).toBeInTheDocument()
    expect(screen.getByText(/Warning 3/)).toBeInTheDocument()
  })

  it('warning de loop: muestra nota de impacto en resultados', () => {
    render(<WarningsBanner warnings={['Loop detectado en "Inspección" forma un ciclo']} />)
    expect(screen.getByText(/Impacto en los resultados/i)).toBeInTheDocument()
  })

  it('warning sin loop: NO muestra nota de impacto', () => {
    render(<WarningsBanner warnings={['Actividad "X" no encontrada en diagrama']} />)
    expect(screen.queryByText(/Impacto en los resultados/i)).not.toBeInTheDocument()
  })
})

// ─── ActivitiesTable ─────────────────────────────────────────────────────────

import { ActivitiesTable } from '@/features/report/ActivitiesTable'

describe('ActivitiesTable', () => {
  it('renderiza exactamente N filas donde N = perActivity.length', () => {
    render(
      <ActivitiesTable
        perActivity={mockActivities}
        mode="relative"
        currency="USD"
        highlightedId={null}
        onRowClick={vi.fn()}
      />
    )
    // getAllByRole('row') incluye el header row, entonces total = N + 1
    const rows = screen.getAllByRole('row')
    expect(rows).toHaveLength(mockActivities.length + 1)
  })

  it('los nombres de las actividades aparecen en la tabla', () => {
    render(
      <ActivitiesTable
        perActivity={mockActivities}
        mode="relative"
        currency="USD"
        highlightedId={null}
        onRowClick={vi.fn()}
      />
    )
    expect(screen.getByText('Recibir solicitud')).toBeInTheDocument()
    expect(screen.getByText('Procesar pedido')).toBeInTheDocument()
    expect(screen.getByText('Notificar cliente')).toBeInTheDocument()
  })

  it('click en fila dispara onRowClick con el bpmnElementId correcto', () => {
    const onRowClick = vi.fn()
    render(
      <ActivitiesTable
        perActivity={mockActivities}
        mode="relative"
        currency="USD"
        highlightedId={null}
        onRowClick={onRowClick}
      />
    )
    const row = screen.getByText('Procesar pedido').closest('tr')!
    fireEvent.click(row)
    expect(onRowClick).toHaveBeenCalledWith('task2')
    expect(onRowClick).toHaveBeenCalledTimes(1)
  })

  it('fila resaltada tiene clase/estado visual distinto', () => {
    render(
      <ActivitiesTable
        perActivity={mockActivities}
        mode="relative"
        currency="USD"
        highlightedId="task1"
        onRowClick={vi.fn()}
      />
    )
    const highlightedRow = screen.getByText('Recibir solicitud').closest('tr')!
    // Verificamos que el atributo data-state='selected' está presente en la fila resaltada
    expect(highlightedRow).toHaveAttribute('data-state', 'selected')
  })

  it('tabla sin actividades: renderiza solo el header (0 data rows)', () => {
    render(
      <ActivitiesTable
        perActivity={[]}
        mode="relative"
        currency="USD"
        highlightedId={null}
        onRowClick={vi.fn()}
      />
    )
    const rows = screen.getAllByRole('row')
    expect(rows).toHaveLength(1) // solo el header
  })
})

// ─── MethodologyFooter ────────────────────────────────────────────────────────

import { MethodologyFooter } from '@/features/report/MethodologyFooter'

describe('MethodologyFooter', () => {
  it('renderiza sin crash con un proceso válido', () => {
    const proc = {
      id: 'p1', name: 'Proceso Test', clientName: '',
      bpmnXml: '<def/>', currency: 'USD',
      overheadPercentage: 0.2, annualFrequency: 1000, analysisHorizonYears: 3, automationInvestment: 0,
      createdAt: 0, updatedAt: 0, groupId: null, tags: [],
    }
    expect(() => render(<MethodologyFooter process={proc} />)).not.toThrow()
  })

  it('muestra el overhead configurado', () => {
    const proc = {
      id: 'p1', name: 'Proc', clientName: '',
      bpmnXml: '', currency: 'USD',
      overheadPercentage: 0.25, annualFrequency: 1000, analysisHorizonYears: 3, automationInvestment: 0,
      createdAt: 0, updatedAt: 0, groupId: null, tags: [],
    }
    render(<MethodologyFooter process={proc} />)
    const text = document.body.textContent ?? ''
    expect(text).toMatch(/25%/)
  })

  it('contiene las palabras clave de la metodología', () => {
    const proc = {
      id: 'p1', name: 'Proc', clientName: '',
      bpmnXml: '', currency: 'USD',
      overheadPercentage: 0.2, annualFrequency: 1000, analysisHorizonYears: 3, automationInvestment: 0,
      createdAt: 0, updatedAt: 0, groupId: null, tags: [],
    }
    render(<MethodologyFooter process={proc} />)
    const text = document.body.textContent ?? ''
    expect(text).toMatch(/Nota metodológica|metodol/i)
    expect(text).toMatch(/probabilidad/i)
  })
})

// ─── ReportPage — empty state (sin simulación) ─────────────────────────────────

vi.mock('@/features/report/useReportData', () => ({
  useReportData: vi.fn(),
}))

import { useReportData } from '@/features/report/useReportData'
import { ReportPage } from '@/features/report/ReportPage'

// Mock de TanStack Router para tests de ReportPage
vi.mock('@tanstack/react-router', async (importActual) => {
  const actual = await importActual<typeof import('@tanstack/react-router')>()
  return {
    ...actual,
    useParams: vi.fn().mockReturnValue({ processId: 'test-proc-id' }),
    useNavigate: vi.fn().mockReturnValue(vi.fn()),
  }
})

describe('ReportPage — empty state (sin simulación)', () => {
  beforeEach(() => {
    vi.mocked(useReportData).mockReturnValue({
      process: null,
      simulation: null,
      activities: [],
      resources: [],
      isLoading: false,
      error: 'Sin simulación disponible. Volvé al workspace y ejecutá "Simular" primero.',
    })
  })

  it('muestra mensaje de error cuando no hay simulación', () => {
    render(<ReportPage />)
    expect(screen.getByText(/Sin simulación disponible/i)).toBeInTheDocument()
  })

  it('muestra botón para ir al workspace', () => {
    render(<ReportPage />)
    const btn = screen.getByText(/Volver a configurar y simular/i)
    expect(btn).toBeInTheDocument()
  })
})

describe('ReportPage — loading state', () => {
  beforeEach(() => {
    vi.mocked(useReportData).mockReturnValue({
      process: null,
      simulation: null,
      activities: [],
      resources: [],
      isLoading: true,
      error: null,
    })
  })

  it('muestra skeleton de carga (no pantalla en blanco)', () => {
    render(<ReportPage />)
    // El skeleton usa animate-pulse en el contenedor
    const skeleton = document.querySelector('.animate-pulse')
    expect(skeleton).not.toBeNull()
  })
})

describe('ReportPage — con datos completos', () => {
  const mockProcess = {
    id: 'test-proc-id',
    name: 'Proceso de Aprobación de Crédito',
    clientName: 'Banco ABC',
    bpmnXml: '<?xml version="1.0"?><definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"><process id="p"><startEvent id="s"><outgoing>f1</outgoing></startEvent><task id="t1" name="Task 1"><incoming>f1</incoming><outgoing>f2</outgoing></task><endEvent id="e"><incoming>f2</incoming></endEvent><sequenceFlow id="f1" sourceRef="s" targetRef="t1"/><sequenceFlow id="f2" sourceRef="t1" targetRef="e"/></process></definitions>',
    currency: 'USD',
    overheadPercentage: 0.2,
    annualFrequency: 1000,
    analysisHorizonYears: 3,
    automationInvestment: 0,
    createdAt: Date.now() - 1000 * 60 * 30,
    updatedAt: Date.now() - 1000 * 60 * 30,
    groupId: null,
    tags: [],
  }

  const mockSimulation = {
    id: 'sim-1',
    processId: 'test-proc-id',
    executedAt: Date.now() - 1000 * 60 * 10,
    result: mockResult,
  }

  beforeEach(() => {
    vi.mocked(useReportData).mockReturnValue({
      process: mockProcess,
      simulation: mockSimulation,
      activities: [],
      resources: [],
      isLoading: false,
      error: null,
    })
  })

  it('renderiza el h1 con el nombre del proceso', () => {
    render(<ReportPage />)
    const h1 = screen.getByRole('heading', { level: 1 })
    expect(h1).toHaveTextContent('Proceso de Aprobación de Crédito')
  })

  it('hay exactamente 4 KPI cards', () => {
    render(<ReportPage />)
    const cards = screen.getAllByTestId('kpi-card')
    expect(cards).toHaveLength(4)
  })

  it('hay al menos 2 gráficos ECharts (TopActivities + Donut siempre; CostByResource solo con recursos)', () => {
    render(<ReportPage />)
    const charts = screen.getAllByTestId('echarts-chart')
    // TopActivities y CostComposition siempre renderizan.
    // CostByResource muestra empty-state cuando resources=[] y sin breakdown.
    expect(charts.length).toBeGreaterThanOrEqual(2)
  })

  it('la tabla tiene N+1 rows (N actividades + 1 header)', () => {
    render(<ReportPage />)
    const rows = screen.getAllByRole('row')
    expect(rows).toHaveLength(mockActivities.length + 1)
  })

  it('el nombre del cliente aparece en el reporte', () => {
    render(<ReportPage />)
    expect(screen.getByText(/Banco ABC/)).toBeInTheDocument()
  })

  it('el timestamp de simulación es visible', () => {
    render(<ReportPage />)
    const text = document.body.textContent ?? ''
    // "Simulado el" debe aparecer en el header
    expect(text).toMatch(/Simulado el/i)
  })

  it('no hay banner de warnings cuando no hay warnings', () => {
    render(<ReportPage />)
    expect(screen.queryByText(/Advertencia/i)).not.toBeInTheDocument()
  })

  it('con warnings: el banner aparece', () => {
    vi.mocked(useReportData).mockReturnValue({
      process: mockProcess,
      simulation: {
        ...mockSimulation,
        result: { ...mockResult, warnings: ['Loop detectado en "Inspección"'] },
      },
      activities: [],
      resources: [],
      isLoading: false,
      error: null,
    })
    render(<ReportPage />)
    expect(screen.getByText(/Loop detectado/)).toBeInTheDocument()
  })
})
