import { renderHook } from '@testing-library/react-hooks';
import { waitFor } from '@testing-library/react';
import { http, HttpResponse } from 'msw';

import { server } from '@CE/setup-tests/server';
import { withTestQueryProvider } from '@CE/react/test-utils/withTestQuery';
import { Stack } from '@CE/react/common/stacks/types';
import { ResourceControlOwnership } from '@CE/react/portainer/access-control/types';

import { useAssociateStackToEnvironmentMutation } from './useAssociateStackToEnvironmentMutation';

function renderMutationHook() {
  const Wrapper = withTestQueryProvider(({ children }) => <>{children}</>);

  return renderHook(() => useAssociateStackToEnvironmentMutation(), {
    wrapper: Wrapper,
  });
}

describe('useAssociateStackToEnvironmentMutation', () => {
  describe('successful association', () => {
    it('should make PUT request to correct endpoint with params', async () => {
      let requestUrl = '';
      let capturedParams: URLSearchParams | undefined;

      server.use(
        http.put('/api/stacks/:id/associate', async ({ request, params }) => {
          requestUrl = request.url;
          capturedParams = new URL(request.url).searchParams;
          return HttpResponse.json({
            Id: Number(params.id),
            Name: 'test-stack',
            ResourceControl: {
              Id: 1,
              ResourceId: Number(params.id),
              Type: 6,
            },
          } as Partial<Stack>);
        }),
        http.put('/api/resource_controls/:id', () =>
          HttpResponse.json({ success: true })
        )
      );

      const { result } = renderMutationHook();

      result.current.mutate({
        environmentId: 5,
        stackId: 123,
        isOrphanedRunning: true,
        accessControl: {
          ownership: ResourceControlOwnership.PRIVATE,
          authorizedUsers: [],
          authorizedTeams: [],
        },
        swarmId: 'swarm-123',
      });

      await waitFor(() => {
        expect(result.current.isSuccess).toBe(true);
      });

      expect(requestUrl).toContain('/api/stacks/123/associate');
      expect(capturedParams?.get('endpointId')).toBe('5');
      expect(capturedParams?.get('orphanedRunning')).toBe('true');
      expect(capturedParams?.get('swarmId')).toBe('swarm-123');
    });

    it('should apply resource control after association', async () => {
      let resourceControlRequestBody: unknown;

      server.use(
        http.put('/api/stacks/:id/associate', () =>
          HttpResponse.json({
            Id: 123,
            Name: 'test-stack',
            ResourceControl: {
              Id: 42,
              ResourceId: 123,
              Type: 6,
            },
          } as Partial<Stack>)
        ),
        http.put('/api/resource_controls/:id', async ({ request, params }) => {
          resourceControlRequestBody = await request.json();
          expect(params.id).toBe('42');
          return HttpResponse.json({ success: true });
        })
      );

      const { result } = renderMutationHook();

      result.current.mutate({
        environmentId: 1,
        stackId: 123,
        accessControl: {
          ownership: ResourceControlOwnership.PRIVATE,
          authorizedUsers: [1, 2],
          authorizedTeams: [3],
        },
      });

      await waitFor(() => {
        expect(result.current.isSuccess).toBe(true);
      });

      expect(resourceControlRequestBody).toBeDefined();
    });

    it('should handle optional swarmId parameter', async () => {
      let capturedParams: URLSearchParams | undefined;

      server.use(
        http.put('/api/stacks/:id/associate', async ({ request }) => {
          capturedParams = new URL(request.url).searchParams;
          return HttpResponse.json({
            Id: 123,
            Name: 'test-stack',
            ResourceControl: {
              Id: 1,
              ResourceId: 123,
              Type: 6,
            },
          } as Partial<Stack>);
        }),
        http.put('/api/resource_controls/:id', () =>
          HttpResponse.json({ success: true })
        )
      );

      const { result } = renderMutationHook();

      result.current.mutate({
        environmentId: 1,
        stackId: 123,
        accessControl: {
          ownership: ResourceControlOwnership.PRIVATE,
          authorizedUsers: [],
          authorizedTeams: [],
        },
        // swarmId is undefined
      });

      await waitFor(() => {
        expect(result.current.isSuccess).toBe(true);
      });

      // swarmId should not be in params when undefined
      expect(capturedParams?.get('swarmId')).toBeNull();
    });

    it('should default orphanedRunning to false when undefined', async () => {
      let capturedParams: URLSearchParams | undefined;

      server.use(
        http.put('/api/stacks/:id/associate', async ({ request }) => {
          capturedParams = new URL(request.url).searchParams;
          return HttpResponse.json({
            Id: 123,
            Name: 'test-stack',
            ResourceControl: {
              Id: 1,
              ResourceId: 123,
              Type: 6,
            },
          } as Partial<Stack>);
        }),
        http.put('/api/resource_controls/:id', () =>
          HttpResponse.json({ success: true })
        )
      );

      const { result } = renderMutationHook();

      result.current.mutate({
        environmentId: 1,
        stackId: 123,
        accessControl: {
          ownership: ResourceControlOwnership.PRIVATE,
          authorizedUsers: [],
          authorizedTeams: [],
        },
        // isOrphanedRunning is undefined
      });

      await waitFor(() => {
        expect(result.current.isSuccess).toBe(true);
      });

      expect(capturedParams?.get('orphanedRunning')).toBe('false');
    });
  });

  describe('error handling', () => {
    let consoleError: ReturnType<typeof vi.spyOn>;

    beforeEach(() => {
      // Suppress console.error for error tests to reduce noise
      consoleError = vi.spyOn(console, 'error').mockImplementation(() => {});
    });

    afterEach(() => {
      consoleError.mockRestore();
    });

    it('should handle API error when association fails', async () => {
      server.use(
        http.put('/api/stacks/:id/associate', () =>
          HttpResponse.json({ message: 'Association failed' }, { status: 500 })
        )
      );

      const { result } = renderMutationHook();

      result.current.mutate({
        environmentId: 1,
        stackId: 123,
        accessControl: {
          ownership: ResourceControlOwnership.PRIVATE,
          authorizedUsers: [],
          authorizedTeams: [],
        },
      });

      await waitFor(() => {
        expect(result.current.isError).toBe(true);
      });

      expect(result.current.error).toBeDefined();
    });

    it('should throw error when ResourceControl is missing from response', async () => {
      server.use(
        http.put('/api/stacks/:id/associate', () =>
          HttpResponse.json({
            Id: 123,
            Name: 'test-stack',
            // ResourceControl is missing
          } as Partial<Stack>)
        )
      );

      const { result } = renderMutationHook();

      result.current.mutate({
        environmentId: 1,
        stackId: 123,
        accessControl: {
          ownership: ResourceControlOwnership.PRIVATE,
          authorizedUsers: [],
          authorizedTeams: [],
        },
      });

      await waitFor(() => {
        expect(result.current.isError).toBe(true);
      });

      expect(result.current.error).toBeDefined();
      expect((result.current.error as Error).message).toContain(
        'resource control expected after creation'
      );
    });

    it('should handle error when applying resource control fails', async () => {
      server.use(
        http.put('/api/stacks/:id/associate', () =>
          HttpResponse.json({
            Id: 123,
            Name: 'test-stack',
            ResourceControl: {
              Id: 1,
              ResourceId: 123,
              Type: 6,
            },
          } as Partial<Stack>)
        ),
        http.put('/api/resource_controls/:id', () =>
          HttpResponse.json(
            { message: 'Failed to update resource control' },
            { status: 500 }
          )
        )
      );

      const { result } = renderMutationHook();

      result.current.mutate({
        environmentId: 1,
        stackId: 123,
        accessControl: {
          ownership: ResourceControlOwnership.PRIVATE,
          authorizedUsers: [],
          authorizedTeams: [],
        },
      });

      await waitFor(() => {
        expect(result.current.isError).toBe(true);
      });

      expect(result.current.error).toBeDefined();
    });
  });

  describe('mutation states', () => {
    it('should track loading state during mutation', async () => {
      server.use(
        http.put('/api/stacks/:id/associate', async () => {
          await new Promise((resolve) => {
            setTimeout(resolve, 100);
          });
          return HttpResponse.json({
            Id: 123,
            Name: 'test-stack',
            ResourceControl: {
              Id: 1,
              ResourceId: 123,
              Type: 6,
            },
          } as Partial<Stack>);
        }),
        http.put('/api/resource_controls/:id', () =>
          HttpResponse.json({ success: true })
        )
      );

      const { result } = renderMutationHook();

      expect(result.current.isLoading).toBe(false);

      result.current.mutate({
        environmentId: 1,
        stackId: 123,
        accessControl: {
          ownership: ResourceControlOwnership.PRIVATE,
          authorizedUsers: [],
          authorizedTeams: [],
        },
      });

      await waitFor(() => {
        expect(result.current.isLoading).toBe(true);
      });

      await waitFor(() => {
        expect(result.current.isSuccess).toBe(true);
      });

      expect(result.current.isLoading).toBe(false);
    });

    it('should return stack data on success', async () => {
      const mockStack = {
        Id: 123,
        Name: 'test-stack',
        ResourceControl: {
          Id: 1,
          ResourceId: 123,
          Type: 6,
        },
      } as Partial<Stack>;

      server.use(
        http.put('/api/stacks/:id/associate', () =>
          HttpResponse.json(mockStack)
        ),
        http.put('/api/resource_controls/:id', () =>
          HttpResponse.json({ success: true })
        )
      );

      const { result } = renderMutationHook();

      result.current.mutate({
        environmentId: 1,
        stackId: 123,
        accessControl: {
          ownership: ResourceControlOwnership.PRIVATE,
          authorizedUsers: [],
          authorizedTeams: [],
        },
      });

      await waitFor(() => {
        expect(result.current.isSuccess).toBe(true);
      });

      expect(result.current.data).toBeUndefined(); // Mutation returns void after applying resource control
    });
  });

  describe('access control integration', () => {
    it('should handle private ownership', async () => {
      let resourceControlBody: unknown;

      server.use(
        http.put('/api/stacks/:id/associate', () =>
          HttpResponse.json({
            Id: 123,
            Name: 'test-stack',
            ResourceControl: {
              Id: 1,
              ResourceId: 123,
              Type: 6,
            },
          } as Partial<Stack>)
        ),
        http.put('/api/resource_controls/:id', async ({ request }) => {
          resourceControlBody = await request.json();
          return HttpResponse.json({ success: true });
        })
      );

      const { result } = renderMutationHook();

      result.current.mutate({
        environmentId: 1,
        stackId: 123,
        accessControl: {
          ownership: ResourceControlOwnership.PRIVATE,
          authorizedUsers: [],
          authorizedTeams: [],
        },
      });

      await waitFor(() => {
        expect(result.current.isSuccess).toBe(true);
      });

      expect(resourceControlBody).toBeDefined();
    });

    it('should handle restricted ownership with users and teams', async () => {
      let resourceControlBody: unknown;

      server.use(
        http.put('/api/stacks/:id/associate', () =>
          HttpResponse.json({
            Id: 123,
            Name: 'test-stack',
            ResourceControl: {
              Id: 1,
              ResourceId: 123,
              Type: 6,
            },
          } as Partial<Stack>)
        ),
        http.put('/api/resource_controls/:id', async ({ request }) => {
          resourceControlBody = await request.json();
          return HttpResponse.json({ success: true });
        })
      );

      const { result } = renderMutationHook();

      result.current.mutate({
        environmentId: 1,
        stackId: 123,
        accessControl: {
          ownership: ResourceControlOwnership.RESTRICTED,
          authorizedUsers: [1, 2, 3],
          authorizedTeams: [10, 20],
        },
      });

      await waitFor(() => {
        expect(result.current.isSuccess).toBe(true);
      });

      expect(resourceControlBody).toBeDefined();
    });
  });
});
