import type { ReactNode } from 'react'
import { screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Position } from 'reactflow'
import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
import CustomEdge from '../custom-edge'
import { BlockEnum, NodeRunningStatus } from '../types'
import { renderWorkflowComponent } from './workflow-test-env'

const mockUseAvailableBlocks = vi.hoisted(() => vi.fn())
const mockUseNodesInteractions = vi.hoisted(() => vi.fn())
const mockGradientRender = vi.hoisted(() => vi.fn())

vi.mock('reactflow', () => ({
  BaseEdge: (props: {
    id: string
    path: string
    style: {
      stroke: string
      strokeWidth: number
      opacity: number
      strokeDasharray?: string
    }
  }) => (
    <div
      data-testid="base-edge"
      data-id={props.id}
      data-path={props.path}
      data-stroke={props.style.stroke}
      data-stroke-width={props.style.strokeWidth}
      data-opacity={props.style.opacity}
      data-dasharray={props.style.strokeDasharray}
    />
  ),
  EdgeLabelRenderer: ({ children }: { children?: ReactNode }) => (
    <div data-testid="edge-label">{children}</div>
  ),
  getBezierPath: () => ['M 0 0', 24, 48],
  Position: {
    Right: 'right',
    Left: 'left',
  },
  useStoreApi: () => ({
    getState: () => ({ getNodes: () => [] }),
  }),
}))

vi.mock('../hooks/use-available-blocks', async (importOriginal) => {
  const actual = await importOriginal<typeof import('../hooks/use-available-blocks')>()

  return {
    ...actual,
    useAvailableBlocks: (...args: unknown[]) => mockUseAvailableBlocks(...args),
  }
})

vi.mock('../hooks/use-nodes-interactions', async (importOriginal) => {
  const actual = await importOriginal<typeof import('../hooks/use-nodes-interactions')>()

  return {
    ...actual,
    useNodesInteractions: () => mockUseNodesInteractions(),
  }
})

vi.mock('@/app/components/workflow/custom-edge-linear-gradient-render', () => ({
  __esModule: true,
  default: (props: { id: string; startColor: string; stopColor: string }) => {
    mockGradientRender(props)
    return <div data-testid="edge-gradient">{props.id}</div>
  },
}))

describe('CustomEdge', () => {
  const mockHandleNodeAdd = vi.fn()

  beforeEach(() => {
    vi.clearAllMocks()
    mockUseNodesInteractions.mockReturnValue({
      handleNodeAdd: mockHandleNodeAdd,
    })
    mockUseAvailableBlocks.mockImplementation((nodeType: BlockEnum) => {
      if (nodeType === BlockEnum.Code) return { availablePrevBlocks: ['code', 'llm'] }

      return { availableNextBlocks: ['llm', 'tool'] }
    })
  })

  it('should render a gradient edge and hide the start tab from its insert-node selector', async () => {
    const user = userEvent.setup()

    renderWorkflowComponent(
      <CustomEdge
        id="edge-1"
        source="source-node"
        sourceHandleId="source"
        target="target-node"
        targetHandleId="target"
        sourceX={100}
        sourceY={120}
        sourcePosition={Position.Right}
        targetX={300}
        targetY={220}
        targetPosition={Position.Left}
        selected={false}
        data={
          {
            sourceType: BlockEnum.Start,
            targetType: BlockEnum.Code,
            _sourceRunningStatus: NodeRunningStatus.Succeeded,
            _targetRunningStatus: NodeRunningStatus.Failed,
            _hovering: true,
            _waitingRun: true,
            _dimmed: true,
            _isTemp: true,
            isInIteration: true,
            isInLoop: true,
          } as never
        }
      />,
    )

    expect(screen.getByTestId('edge-gradient')).toHaveTextContent('edge-1')
    expect(mockGradientRender).toHaveBeenCalledWith(
      expect.objectContaining({
        id: 'edge-1',
        startColor: 'var(--color-workflow-link-line-success-handle)',
        stopColor: 'var(--color-workflow-link-line-error-handle)',
      }),
    )
    expect(screen.getByTestId('base-edge')).toHaveAttribute('data-stroke', 'url(#edge-1)')
    expect(screen.getByTestId('base-edge')).toHaveAttribute('data-opacity', '0.3')
    expect(screen.getByTestId('base-edge')).toHaveAttribute('data-dasharray', '8 8')
    const addBlockTrigger = screen.getByRole('button', { name: 'workflow.common.addBlock' })
    expect(addBlockTrigger.parentElement).toHaveStyle({
      transform: 'translate(-50%, -50%) translate(24px, 48px)',
      opacity: '0.7',
      zIndex: '1001',
    })

    await user.click(addBlockTrigger)

    expect(screen.queryByRole('tab', { name: 'workflow.tabs.start' })).not.toBeInTheDocument()
  })

  it('should prefer the running stroke color when the edge is selected', () => {
    renderWorkflowComponent(
      <CustomEdge
        id="edge-selected"
        source="source-node"
        target="target-node"
        sourceX={0}
        sourceY={0}
        sourcePosition={Position.Right}
        targetX={100}
        targetY={100}
        targetPosition={Position.Left}
        selected
        data={
          {
            sourceType: BlockEnum.Start,
            targetType: BlockEnum.Code,
            _sourceRunningStatus: NodeRunningStatus.Succeeded,
            _targetRunningStatus: NodeRunningStatus.Running,
          } as never
        }
      />,
    )

    expect(screen.getByTestId('base-edge')).toHaveAttribute(
      'data-stroke',
      'var(--color-workflow-link-line-handle)',
    )
  })

  it('should use the fail-branch running color while the connected node is hovering', () => {
    renderWorkflowComponent(
      <CustomEdge
        id="edge-hover"
        source="source-node"
        sourceHandleId={ErrorHandleTypeEnum.failBranch}
        target="target-node"
        sourceX={0}
        sourceY={0}
        sourcePosition={Position.Right}
        targetX={100}
        targetY={100}
        targetPosition={Position.Left}
        selected={false}
        data={
          {
            sourceType: BlockEnum.Start,
            targetType: BlockEnum.Code,
            _connectedNodeIsHovering: true,
          } as never
        }
      />,
    )

    expect(screen.getByTestId('base-edge')).toHaveAttribute(
      'data-stroke',
      'var(--color-workflow-link-line-failure-handle)',
    )
  })

  it('should fall back to the default edge color when no highlight state is active', () => {
    renderWorkflowComponent(
      <CustomEdge
        id="edge-default"
        source="source-node"
        target="target-node"
        sourceX={0}
        sourceY={0}
        sourcePosition={Position.Right}
        targetX={100}
        targetY={100}
        targetPosition={Position.Left}
        selected={false}
        data={
          {
            sourceType: BlockEnum.Start,
            targetType: BlockEnum.Code,
          } as never
        }
      />,
    )

    expect(screen.getByTestId('base-edge')).toHaveAttribute(
      'data-stroke',
      'var(--color-workflow-link-line-normal)',
    )
    expect(
      screen.getByRole('button', { name: 'workflow.common.addBlock' }).parentElement,
    ).toHaveStyle({
      opacity: '0',
      pointerEvents: 'none',
    })
  })
})
