import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { http, HttpResponse } from 'msw';
import { vi, describe, it, expect, beforeEach } from 'vitest';

import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import { withTestRouter } from '@/react/test-utils/withRouter';
import { withUserProvider } from '@/react/test-utils/withUserProvider';
import { UserViewModel } from '@/portainer/models/user';
import { server } from '@/setup-tests/server';
import { useAuthorizations } from '@/react/hooks/useUser';
import { select } from '@/react/test-utils/react-select';

import { confirmUpdateNode } from '../ConfirmUpdateNode';

import { NodeDetails } from './NodeDetails';

// Mocks

vi.mock('../ConfirmUpdateNode', () => ({
  confirmUpdateNode: vi.fn(),
}));

vi.mock('@/portainer/services/notifications', () => ({
  notifySuccess: vi.fn(),
}));

vi.mock(
  '@/react/hooks/useUser',
  async (importOriginal: () => Promise<object>) => {
    const actual = await importOriginal();
    return {
      ...actual,
      useAuthorizations: vi.fn(),
    };
  }
);

vi.mock('@uirouter/react', async (importOriginal: () => Promise<object>) => ({
  ...(await importOriginal()),

  useCurrentStateAndParams: vi.fn(() => ({
    params: { endpointId: 1, name: 'test-node' },
  })),

  useRouter: vi.fn(() => ({
    stateService: {
      reload: vi.fn(),
    },
  })),
}));

// Sample test data
const mockNode = {
  metadata: {
    name: 'test-node',
    labels: {
      'kubernetes.io/hostname': 'test-node',
      'node-role.kubernetes.io/control-plane': '',
      'custom-label': 'custom-value',
    },
  },
  spec: {
    taints: [
      {
        key: 'node-role.kubernetes.io/control-plane',
        effect: 'NoSchedule',
        value: '',
      },
    ],
    unschedulable: false,
  },
  status: {
    conditions: [
      {
        type: 'Ready',
        status: 'True',
      },
    ],
    allocatable: {
      memory: '2Gi',
      cpu: '2',
    },
  },
};

const mockNodes = [
  mockNode,
  {
    metadata: { name: 'node-2' },
    spec: { unschedulable: false },
    status: {
      conditions: [{ type: 'Ready', status: 'True' }],
      allocatable: {
        memory: '2Gi',
        cpu: '2',
      },
    },
  },
];

const mockApplications = [
  {
    Name: 'test-app',
    Namespace: 'default',
  },
];

const mockPortainerApplications = [
  {
    Name: 'portainer',
    Namespace: 'portainer',
  },
];

const mockEndpoints: unknown[] = [];

function setupMocks({
  applications = mockApplications,
  nodes = mockNodes,
  hasWriteAccess = true,
} = {}) {
  vi.mocked(useAuthorizations).mockReturnValue({
    authorized: hasWriteAccess,
    isLoading: false,
  });

  server.use(
    http.get('/api/endpoints/1/kubernetes/api/v1/nodes/test-node', () =>
      HttpResponse.json(mockNode)
    ),
    http.get('/api/kubernetes/1/nodes', () => HttpResponse.json(nodes)),
    http.get('/api/kubernetes/1/applications', () =>
      HttpResponse.json(applications)
    ),
    http.get('/api/endpoints/1/kubernetes/api/v1/endpoints', () =>
      HttpResponse.json({ items: mockEndpoints })
    ),
    http.patch('/api/endpoints/1/kubernetes/api/v1/nodes/test-node', () =>
      HttpResponse.json(mockNode)
    ),
    http.post('/api/endpoints/1/kubernetes/api/v1/nodes/test-node/drain', () =>
      HttpResponse.json({})
    ),
    http.get('/api/kubernetes/1/metrics/nodes/test-node', () =>
      HttpResponse.json({
        usage: {
          memory: '1000000000',
          cpu: '1000m',
        },
      })
    ),
    http.get('/api/kubernetes/1/metrics/applications_resources', () =>
      HttpResponse.json({
        MemoryRequest: 500000000,
        CpuRequest: 500,
      })
    )
  );
}

function renderComponent() {
  const user = new UserViewModel({ Username: 'admin' });

  const Wrapped = withTestQueryProvider(
    withUserProvider(
      withTestRouter(() => (
        <NodeDetails nodeName="test-node" environmentId={1} />
      )),
      user
    )
  );

  return render(<Wrapped />);
}

describe('NodeDetails', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    setupMocks();
  });

  it('shows drain warning when selecting Drain availability', async () => {
    const user = userEvent.setup();
    renderComponent();

    // Wait for component to load
    await waitFor(() => {
      expect(screen.getByText('test-node')).toBeInTheDocument();
    });

    // Find the availability select and select Drain
    const availabilitySelect = screen.getByLabelText('Availability');
    await select(availabilitySelect, 'Drain', { user });

    // Try to submit the form to trigger validation
    const submitButton = screen.getByRole('button', { name: /update node/i });
    await user.click(submitButton);

    // Check that the confirmation modal is called with drain warning
    await waitFor(() => {
      expect(vi.mocked(confirmUpdateNode)).toHaveBeenCalledWith(
        false, // taintsWarning
        false, // labelsWarning
        false, // cordonWarning
        true // drainWarning
      );
    });
  });

  it('prevents submission when Portainer is running on node', async () => {
    const user = userEvent.setup();
    setupMocks({ applications: mockPortainerApplications });

    renderComponent();

    await waitFor(() => {
      expect(screen.getByText('test-node')).toBeInTheDocument();
    });

    const availabilitySelect = screen.getByLabelText('Availability');
    await select(availabilitySelect, 'Drain', { user });

    await waitFor(() => {
      expect(
        screen.getByText(/cannot drain node where.*portainer.*running/i)
      ).toBeInTheDocument();
    });

    const submitButton = screen.getByRole('button', { name: /update node/i });
    expect(submitButton).toBeDisabled();
  });

  it('prevents drain when only one node in cluster', async () => {
    const user = userEvent.setup();
    setupMocks({ nodes: [mockNode] });

    renderComponent();

    await waitFor(() => {
      expect(screen.getByText('test-node')).toBeInTheDocument();
    });

    const availabilitySelect = screen.getByLabelText('Availability');
    await select(availabilitySelect, 'Drain', { user });

    await waitFor(() => {
      expect(
        screen.getByText(/cannot drain.*only node.*cluster/i)
      ).toBeInTheDocument();
    });

    const submitButton = screen.getByRole('button', { name: /update node/i });
    expect(submitButton).toBeDisabled();
  });

  it('prevents drain when another node is already draining', async () => {
    const user = userEvent.setup();
    const drainingNodes = [
      mockNode,
      {
        ...mockNodes[1],
        metadata: {
          ...mockNodes[1].metadata,
          labels: {
            'io.portainer/node-status-drain': '',
          },
        },
        spec: { unschedulable: true },
      },
    ];
    setupMocks({ nodes: drainingNodes });

    renderComponent();

    await waitFor(() => {
      expect(screen.getByText('test-node')).toBeInTheDocument();
    });

    const availabilitySelect = screen.getByLabelText('Availability');
    await select(availabilitySelect, 'Drain', { user });

    await waitFor(() => {
      expect(
        screen.getByText(/cannot drain.*another node.*currently.*drained/i)
      ).toBeInTheDocument();
    });
  });

  it('shows cordon warning when submitting with Pause availability', async () => {
    const user = userEvent.setup();
    vi.mocked(confirmUpdateNode).mockResolvedValue(true);

    renderComponent();

    await waitFor(() => {
      expect(screen.getByText('test-node')).toBeInTheDocument();
    });

    const availabilitySelect = screen.getByLabelText('Availability');
    await select(availabilitySelect, 'Pause', { user });

    const submitButton = screen.getByRole('button', { name: /update node/i });
    await user.click(submitButton);

    // Verify confirmation modal was called with cordon warning
    await waitFor(() => {
      expect(vi.mocked(confirmUpdateNode)).toHaveBeenCalledWith(
        false, // taintsWarning
        false, // labelsWarning
        true, // cordonWarning
        false // drainWarning
      );
    });
  });

  it('does not show form actions when user lacks write access', async () => {
    setupMocks({ hasWriteAccess: false });

    renderComponent();

    await waitFor(() => {
      expect(screen.getByText('test-node')).toBeInTheDocument();
    });

    // Form actions should not be present
    expect(
      screen.queryByRole('button', { name: /update node/i })
    ).not.toBeInTheDocument();
    expect(
      screen.queryByRole('button', { name: /cancel/i })
    ).not.toBeInTheDocument();
  });
});
