import { render, screen, waitFor } from '@testing-library/react';
import { http, HttpResponse } from 'msw';
import userEvent from '@testing-library/user-event';
import { JSXElementConstructor, ReactElement } from 'react';

import { withUserProvider } from '@/react/test-utils/withUserProvider';
import { withTestRouter } from '@/react/test-utils/withRouter';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import { createMockUser } from '@/react-tools/test-mocks';
import { server } from '@/setup-tests/server';
import { User } from '@/portainer/users/types';

import { NameRow } from './NameRow';

vi.mock('@uirouter/react', async (importOriginal: () => Promise<object>) => ({
  ...(await importOriginal()),
  useCurrentStateAndParams: vi.fn(() => ({
    params: { endpointId: 1 },
  })),
}));

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

  describe('Display Mode', () => {
    it('displays container name with leading slash trimmed', () => {
      // Test with leading slash
      const { rerender } = renderComponent({ containerName: '/my-container' });
      expect(screen.getByText('my-container')).toBeVisible();
      expect(screen.queryByText('/my-container')).not.toBeInTheDocument();

      // Test without leading slash
      rerenderComponent(rerender, {
        props: {
          containerName: 'already-trimmed',
        },
      });

      expect(screen.getByText('already-trimmed')).toBeVisible();
    });

    it('shows edit button for admin user', async () => {
      const adminUser = createMockUser({ Role: 1 });
      renderComponent({}, adminUser);
      expect(
        await screen.findByRole('button', { name: /edit container name/i })
      ).toBeVisible();
    });

    it('shows edit button for non-admin user with authorization', async () => {
      const authorizedUser = createMockUser({
        Role: 2,
        EndpointAuthorizations: {
          1: {
            DockerContainerRename: true,
          },
        },
      });
      renderComponent({}, authorizedUser);

      await waitFor(async () => {
        expect(
          screen.getByRole('button', { name: /edit container name/i })
        ).toBeVisible();
      });
    });

    it('shows edit button for non-admin user without authorization (CE)', async () => {
      const unauthorizedUser = createMockUser({
        Role: 2,
        EndpointAuthorizations: {
          1: {
            DockerContainerRename: false,
          },
        },
      });
      renderComponent({}, unauthorizedUser);
      expect(
        await screen.findByRole('button', { name: /edit container name/i })
      ).toBeVisible();
    });
  });

  describe('Edit Mode', () => {
    it('enters edit mode with focused input pre-filled with trimmed name', async () => {
      renderComponent({ containerName: '/original-name' });

      const editButton = await screen.findByRole('button', {
        name: /edit container name/i,
      });
      await userEvent.click(editButton);

      const nameInput = screen.getByTestId('containerNameInput');
      expect(nameInput).toBeVisible();
      expect(nameInput).toHaveFocus();
      expect(nameInput).toHaveValue('original-name');
    });

    it('shows cancel and confirm buttons in edit mode', async () => {
      renderComponent();

      const editButton = await screen.findByRole('button', {
        name: /edit container name/i,
      });
      await userEvent.click(editButton);

      expect(
        screen.getByRole('button', { name: /cancel container name edit/i })
      ).toBeVisible();
      expect(
        screen.getByRole('button', { name: /rename container/i })
      ).toBeVisible();
    });

    it('returns to display mode when cancel button is clicked', async () => {
      renderComponent({ containerName: '/my-container' });

      const editButton = await screen.findByRole('button', {
        name: /edit container name/i,
      });
      await userEvent.click(editButton);

      const cancelButton = screen.getByRole('button', {
        name: /cancel container name edit/i,
      });
      await userEvent.click(cancelButton);

      expect(screen.getByText('my-container')).toBeVisible();
      expect(
        screen.queryByTestId('containerNameInput')
      ).not.toBeInTheDocument();
    });

    it('allows editing the container name', async () => {
      renderComponent();

      const editButton = await screen.findByRole('button', {
        name: /edit container name/i,
      });
      await userEvent.click(editButton);

      const nameInput = screen.getByTestId('containerNameInput');
      await userEvent.clear(nameInput);
      await userEvent.type(nameInput, 'new-container-name');

      expect(nameInput).toHaveValue('new-container-name');
    });
  });

  describe('Rename Functionality', () => {
    it('successfully renames container with correct API parameters and returns to display mode', async () => {
      let capturedRequest: {
        endpointId: string | null;
        containerId: string | null;
        nodeNameHeader: string | null;
        name: string | null;
      } | null = null;

      const onSuccess = vi.fn();

      server.use(
        http.post(
          '/api/endpoints/:endpointId/docker/containers/:id/rename',
          async ({ params, request }) => {
            const url = new URL(request.url);
            capturedRequest = {
              endpointId: params.endpointId as string,
              containerId: params.id as string,
              nodeNameHeader: request.headers.get('X-PortainerAgent-Target'),
              name: url.searchParams.get('name'),
            };
            return HttpResponse.json({});
          }
        )
      );

      renderComponent({
        containerId: 'container-abc',
        containerName: '/original-name',
        environmentId: 5,
        nodeName: 'swarm-node-2',
        onSuccess,
      });

      const editButton = await screen.findByRole('button', {
        name: /edit container name/i,
      });
      await userEvent.click(editButton);

      const nameInput = screen.getByTestId('containerNameInput');
      await userEvent.clear(nameInput);
      await userEvent.type(nameInput, 'updated-name');

      const confirmButton = screen.getByTestId(
        'container-confirm-rename-button'
      );
      expect(confirmButton).not.toBeDisabled();

      await userEvent.click(confirmButton);

      // Button should be disabled during submission
      expect(confirmButton).toBeDisabled();

      // Should call onSuccess callback after successful rename
      await waitFor(() => {
        expect(onSuccess).toHaveBeenCalledTimes(1);
      });

      // Should return to display mode
      await waitFor(() => {
        expect(
          screen.queryByTestId('containerNameInput')
        ).not.toBeInTheDocument();
      });

      // Verify API was called with correct parameters
      expect(capturedRequest).toEqual({
        endpointId: '5',
        containerId: 'container-abc',
        nodeNameHeader: 'swarm-node-2',
        name: 'updated-name',
      });
    });

    it('does not make API call if name is unchanged', async () => {
      const onSuccess = vi.fn();
      let apiCallCount = 0;

      server.use(
        http.post(
          '/api/endpoints/:endpointId/docker/containers/:id/rename',
          () => {
            apiCallCount += 1;
            return HttpResponse.json({});
          }
        )
      );

      renderComponent({
        containerName: '/same-name',
        onSuccess,
      });

      const editButton = await screen.findByRole('button', {
        name: /edit container name/i,
      });
      await userEvent.click(editButton);

      const confirmButton = screen.getByTestId(
        'container-confirm-rename-button'
      );
      await userEvent.click(confirmButton);

      await waitFor(() => {
        expect(screen.getByText('same-name')).toBeVisible();
      });

      expect(apiCallCount).toBe(0);
      expect(onSuccess).not.toHaveBeenCalled();
    });

    it('handles rename API error gracefully', async () => {
      // Mock console.error to suppress expected error logs
      const consoleErrorSpy = vi
        .spyOn(console, 'error')
        .mockImplementation(() => {});

      server.use(
        http.post(
          '/api/endpoints/:endpointId/docker/containers/:id/rename',
          () =>
            HttpResponse.json(
              { message: 'Container rename failed' },
              { status: 500 }
            )
        )
      );

      renderComponent({ containerName: '/original-name' });

      const editButton = await screen.findByRole('button', {
        name: /edit container name/i,
      });
      await userEvent.click(editButton);

      const nameInput = screen.getByTestId('containerNameInput');
      await userEvent.clear(nameInput);
      await userEvent.type(nameInput, 'new-name');

      const confirmButton = screen.getByTestId(
        'container-confirm-rename-button'
      );
      await userEvent.click(confirmButton);

      expect(confirmButton).toBeDisabled();

      await new Promise((resolve) => {
        setTimeout(resolve, 100);
      });

      // Should still be in edit mode with the form visible (not closed on error)
      expect(screen.getByTestId('containerNameInput')).toBeVisible();
      expect(nameInput).toHaveValue('new-name');

      consoleErrorSpy.mockRestore();
    });

    it('validates that container name is required', async () => {
      renderComponent({ containerName: '/original-name' });

      const editButton = await screen.findByRole('button', {
        name: /edit container name/i,
      });
      await userEvent.click(editButton);

      const nameInput = screen.getByTestId('containerNameInput');
      await userEvent.clear(nameInput);

      const confirmButton = screen.getByTestId(
        'container-confirm-rename-button'
      );
      await userEvent.click(confirmButton);

      expect(
        screen.queryByText(/successfully renamed/i)
      ).not.toBeInTheDocument();
    });
  });
});

type RenderProps = Partial<{
  containerId: string;
  containerName: string;
  environmentId: number;
  nodeName: string;
  onSuccess: () => void;
}>;

function createWrappedComponent(
  props: RenderProps,
  user = createMockUser({ Role: 1 })
) {
  const defaultProps = {
    containerId: 'container-123',
    containerName: '/test-container',
    environmentId: 1,
    nodeName: 'node-1',
    onSuccess: vi.fn(),
  };

  const mergedProps = { ...defaultProps, ...props };

  const Wrapped = withTestQueryProvider(
    withTestRouter(withUserProvider(NameRow, user), {
      route: 'docker.containers.container',
      stateConfig: [
        {
          name: 'docker.containers.container',
          url: '/docker/:endpointId/containers/:id',
          params: { endpointId: '1', id: mergedProps.containerId },
        },
      ],
    })
  );

  return { Wrapped, mergedProps };
}

function renderComponent(
  props: RenderProps = {},
  user = createMockUser({ Role: 1 })
) {
  const { Wrapped, mergedProps } = createWrappedComponent(props, user);
  return render(<Wrapped {...mergedProps} />);
}

function rerenderComponent(
  rerender: (
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    ui: ReactElement<any, string | JSXElementConstructor<any>>
  ) => void,
  { props = {}, user }: { props?: RenderProps; user?: User } = {}
) {
  const { Wrapped, mergedProps } = createWrappedComponent(props, user);
  rerender(<Wrapped {...mergedProps} />);
}
