// @vitest-environment jsdom
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import React from 'react';
import SystemPulseFlow from './Flow';
import { getAgentIcon, getAgentDescription } from './FlowParts';
import { logger } from '@claw/core/lib/logger';

vi.mock('@/components/Providers/TranslationsProvider', () => ({
  useTranslations: () => ({
    t: (key: string) => key,
    locale: 'en',
    setLocale: vi.fn(),
  }),
}));

vi.mock('@claw/core/lib/logger', () => ({
  logger: {
    info: vi.fn(),
    warn: vi.fn(),
    error: vi.fn(),
  },
}));

const mockFitView = vi.fn();

// Mock @xyflow/react
vi.mock('@xyflow/react', () => {
  return {
    ReactFlow: ({
      nodes,
      edges,
      children,
    }: {
      nodes: unknown[];
      edges: unknown[];
      children: React.ReactNode;
    }) => (
      <div
        data-testid="react-flow"
        data-nodes={JSON.stringify(nodes)}
        data-edges={JSON.stringify(edges)}
      >
        {children}
      </div>
    ),
    Background: () => <div data-testid="background" />,
    Handle: () => <div data-testid="handle" />,
    Position: { Top: 'top', Bottom: 'bottom', Left: 'left', Right: 'right' },
    MarkerType: { ArrowClosed: 'arrowclosed' },
    useNodesState: (initialNodes: unknown) => {
      const [nodes, setNodes] = React.useState(initialNodes);
      const onNodesChange = vi.fn();
      return [nodes, setNodes, onNodesChange];
    },
    useEdgesState: (initialEdges: unknown) => {
      const [edges, setEdges] = React.useState(initialEdges);
      const onEdgesChange = vi.fn();
      return [edges, setEdges, onEdgesChange];
    },
    useReactFlow: () => ({
      zoomIn: vi.fn(),
      zoomOut: vi.fn(),
      fitView: mockFitView,
    }),
    ReactFlowProvider: ({ children }: { children: React.ReactNode }) => (
      <div data-testid="react-flow-provider">{children}</div>
    ),
  };
});

// Mock UI components
vi.mock('@/components/ui/Button', () => ({
  default: ({
    children,
    onClick,
    icon,
    title,
  }: {
    children: React.ReactNode;
    onClick: () => void;
    icon: React.ReactNode;
    title: string;
  }) => (
    <button onClick={onClick} data-testid="button" title={title}>
      {icon}
      {children}
    </button>
  ),
}));

vi.mock('@/components/ui/Typography', () => ({
  default: ({ children }: { children: React.ReactNode }) => (
    <div data-testid="typography">{children}</div>
  ),
}));

vi.mock('@/components/ui/Card', () => ({
  default: ({ children }: { children: React.ReactNode }) => (
    <div data-testid="card">{children}</div>
  ),
}));

// Mock fetch
const mockFetch = vi.fn();
global.fetch = mockFetch;

describe('Flow Component Logic', () => {
  describe('getAgentIcon', () => {
    it('returns icon for known agent IDs', () => {
      const icon = getAgentIcon('superclaw');
      expect(icon).toBeDefined();
    });

    it('returns icon for known icon keys', () => {
      const icon = getAgentIcon('unknown', 'BOT');
      expect(icon).toBeDefined();
    });

    it('returns fallback icon for unknown agents', () => {
      const icon = getAgentIcon('mystery-agent');
      expect(icon).toBeDefined();
    });
  });

  describe('getAgentDescription', () => {
    it('returns description for known agent IDs', () => {
      expect(getAgentDescription('superclaw')).toContain('orchestrates task delegation');
      expect(getAgentDescription('coder')).toContain('code generation');
    });

    it('returns fallback description for unknown agents', () => {
      expect(getAgentDescription('unknown')).toContain('Dynamic neural spoke');
    });
  });
});

describe('SystemPulseFlow Component', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('renders loading state initially', async () => {
    mockFetch.mockReturnValue(new Promise(() => {})); // Never resolves
    render(<SystemPulseFlow />);
    expect(screen.getByText(/Initializing_Neural_Grid/i)).toBeInTheDocument();
  });

  it('fetches and renders topology data', async () => {
    const mockData = {
      nodes: [
        { id: 'superclaw', type: 'agent', tier: 'APP', label: 'SuperClaw' },
        { id: 'bus', type: 'bus', tier: 'COMM', label: 'Message Bus' },
      ],
      edges: [{ id: 'e1', source: 'superclaw', target: 'bus', label: 'ORCHESTRATE' }],
    };

    mockFetch.mockResolvedValue({
      ok: true,
      json: async () => mockData,
    });

    render(<SystemPulseFlow />);

    await waitFor(() => {
      expect(screen.queryByText(/Initializing_Neural_Grid/i)).not.toBeInTheDocument();
    });

    const flow = screen.getByTestId('react-flow');
    const nodes = JSON.parse(flow.getAttribute('data-nodes') || '[]');
    const edges = JSON.parse(flow.getAttribute('data-edges') || '[]');

    expect(nodes).toHaveLength(2);
    expect(nodes[0].id).toBe('superclaw');
    expect(nodes[1].id).toBe('bus');

    expect(edges).toHaveLength(1);
    expect(edges[0].source).toBe('superclaw');
    expect(edges[0].target).toBe('bus');
  });

  it('handles diverse tiers and types in topology data', async () => {
    const mockData = {
      nodes: [
        { id: 'n1', type: 'agent', tier: 'APP', label: 'AppNode', enabled: true },
        { id: 'n2', type: 'bus', tier: 'COMM', label: 'CommNode' },
        { id: 'n3', type: 'infra', tier: 'INFRA', label: 'InfraNode' },
        { id: 'n4', type: 'agent', tier: 'AGENT', label: 'AgentNode' },
        { id: 'n5', type: 'unknown', tier: 'INFRA', label: 'OtherNode' },
        { id: 'n6', type: 'agent', tier: 'APP', label: 'OfflineNode', enabled: false },
      ],
      edges: [{ id: 'e1', source: 'n1', target: 'n2' }],
    };

    mockFetch.mockResolvedValue({
      ok: true,
      json: async () => mockData,
    });

    render(<SystemPulseFlow />);

    await waitFor(() => {
      expect(screen.queryByText(/Initializing_Neural_Grid/i)).not.toBeInTheDocument();
    });

    const flow = screen.getByTestId('react-flow');
    const nodes = JSON.parse(flow.getAttribute('data-nodes') || '[]');
    expect(nodes).toHaveLength(6);

    // Verify Tiered Positioning
    const n1 = nodes.find((n: { id: string }) => n.id === 'n1'); // APP
    const n2 = nodes.find((n: { id: string }) => n.id === 'n2'); // COMM
    const n3 = nodes.find((n: { id: string }) => n.id === 'n3'); // INFRA
    const n4 = nodes.find((n: { id: string }) => n.id === 'n4'); // AGENT

    expect(n1?.position.y).toBe(0);
    expect(n2?.position.y).toBe(440); // 2 * 220
    expect(n3?.position.y).toBe(1100); // 5 * 220
    expect(n4?.position.y).toBe(660); // 3 * 220
  });

  it('applies correct styling to edges based on source type', async () => {
    const mockData = {
      nodes: [
        { id: 'bus', type: 'bus', tier: 'COMM', label: 'Message Bus' },
        { id: 'agent', type: 'agent', tier: 'AGENT', label: 'Agent' },
      ],
      edges: [
        { id: 'e1', source: 'bus', target: 'agent' },
        { id: 'e2', source: 'agent', target: 'bus' },
      ],
    };

    mockFetch.mockResolvedValue({
      ok: true,
      json: async () => mockData,
    });

    render(<SystemPulseFlow />);

    await waitFor(() => {
      expect(screen.queryByText(/Initializing_Neural_Grid/i)).not.toBeInTheDocument();
    });

    const flow = screen.getByTestId('react-flow');
    const edges = JSON.parse(flow.getAttribute('data-edges') || '[]');

    // Edge from Bus should be Vivid Orange (#f97316)
    const busEdge = edges.find((e: { id: string }) => e.id === 'e1');
    expect(busEdge?.style.stroke).toBe('#f97316');

    // Edge from Agent should be Cyber Blue (#00f3ff)
    const agentEdge = edges.find((e: { id: string }) => e.id === 'e2');
    expect(agentEdge?.style.stroke).toBe('#00f3ff');
  });

  it('handles fetch error gracefully', async () => {
    const loggerSpy = vi.spyOn(logger, 'error').mockImplementation(() => {});
    mockFetch.mockRejectedValue(new Error('Fetch failed'));

    render(<SystemPulseFlow />);

    await waitFor(() => {
      expect(screen.queryByText(/Initializing_Neural_Grid/i)).not.toBeInTheDocument();
    });

    expect(loggerSpy).toHaveBeenCalledWith('[Topology] Fetch error:', expect.any(Error));
    loggerSpy.mockRestore();
  });

  it('re-fetches data and fits view when reset button is clicked', async () => {
    mockFetch.mockResolvedValue({
      ok: true,
      json: async () => ({ nodes: [], edges: [] }),
    });

    render(<SystemPulseFlow />);

    await waitFor(() => {
      expect(screen.queryByText(/Initializing_Neural_Grid/i)).not.toBeInTheDocument();
    });

    // Reset fetch mock to track new calls
    mockFetch.mockClear();

    const resetButton = screen.getByTitle('Reset View & Layout');
    fireEvent.click(resetButton);

    expect(screen.getByText(/Initializing_Neural_Grid/i)).toBeInTheDocument();
    expect(mockFetch).toHaveBeenCalledTimes(1);

    await waitFor(() => {
      expect(screen.queryByText(/Initializing_Neural_Grid/i)).not.toBeInTheDocument();
    });

    // Wait for setTimeout(() => fitView(), 100)
    await new Promise((resolve) => setTimeout(resolve, 150));
    expect(mockFitView).toHaveBeenCalled();
  });
});
