import type { ILoadOptionsFunctions } from 'n8n-workflow';

import { getUsers, getRepositories, getWorkflows, getRefs } from '../SearchFunctions';
import type { Mock } from 'vitest';

const mockLoadOptionsFunctions = {
	getNodeParameter: vi.fn(),
	getCredentials: vi.fn().mockResolvedValue({
		server: 'https://api.github.com',
	}),
	helpers: {
		requestWithAuthentication: vi.fn(),
	},
	getCurrentNodeParameter: vi.fn(),
} as unknown as ILoadOptionsFunctions;

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

	describe('getUsers', () => {
		it('should fetch users', async () => {
			const filter = 'test-user';
			const responseData = {
				items: [
					{ login: 'test-user-1', html_url: 'https://github.com/test-user-1' },
					{ login: 'test-user-2', html_url: 'https://github.com/test-user-2' },
				],
				total_count: 2,
			};

			(mockLoadOptionsFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
				responseData,
			);

			const result = await getUsers.call(mockLoadOptionsFunctions, filter);

			expect(result).toEqual({
				results: [
					{ name: 'test-user-1', value: 'test-user-1', url: 'https://github.com/test-user-1' },
					{ name: 'test-user-2', value: 'test-user-2', url: 'https://github.com/test-user-2' },
				],
				paginationToken: undefined,
			});

			expect(mockLoadOptionsFunctions.helpers.requestWithAuthentication).toHaveBeenCalledWith(
				'githubOAuth2Api',
				expect.objectContaining({
					method: 'GET',
					qs: expect.objectContaining({ page: 1 }),
				}),
			);
		});

		it('should handle pagination', async () => {
			const filter = 'test-user';
			const responseData = {
				items: [
					{ login: 'test-user-1', html_url: 'https://github.com/test-user-1' },
					{ login: 'test-user-2', html_url: 'https://github.com/test-user-2' },
				],
				total_count: 200,
			};

			(mockLoadOptionsFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
				responseData,
			);

			const result = await getUsers.call(mockLoadOptionsFunctions, filter);

			expect(result).toEqual({
				results: [
					{ name: 'test-user-1', value: 'test-user-1', url: 'https://github.com/test-user-1' },
					{ name: 'test-user-2', value: 'test-user-2', url: 'https://github.com/test-user-2' },
				],
				paginationToken: '2',
			});
		});

		it('should use paginationToken when provided', async () => {
			const filter = 'test-user';
			const paginationToken = '3';
			const responseData = {
				items: [
					{ login: 'test-user-5', html_url: 'https://github.com/test-user-5' },
					{ login: 'test-user-6', html_url: 'https://github.com/test-user-6' },
				],
				total_count: 200,
			};

			(mockLoadOptionsFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
				responseData,
			);

			const result = await getUsers.call(mockLoadOptionsFunctions, filter, paginationToken);

			expect(result).toEqual({
				results: [
					{ name: 'test-user-5', value: 'test-user-5', url: 'https://github.com/test-user-5' },
					{ name: 'test-user-6', value: 'test-user-6', url: 'https://github.com/test-user-6' },
				],
				paginationToken: undefined,
			});

			expect(mockLoadOptionsFunctions.helpers.requestWithAuthentication).toHaveBeenCalledWith(
				'githubOAuth2Api',
				expect.objectContaining({
					method: 'GET',
					qs: expect.objectContaining({ page: 3 }),
				}),
			);
		});
	});

	describe('getRepositories', () => {
		it('should fetch repositories', async () => {
			const filter = 'test-repo';
			const owner = 'test-owner';
			const responseData = {
				items: [
					{ name: 'test-repo-1', html_url: 'https://github.com/test-owner/test-repo-1' },
					{ name: 'test-repo-2', html_url: 'https://github.com/test-owner/test-repo-2' },
				],
				total_count: 2,
			};

			(mockLoadOptionsFunctions.getCurrentNodeParameter as Mock).mockReturnValue(owner);
			(mockLoadOptionsFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
				responseData,
			);

			const result = await getRepositories.call(mockLoadOptionsFunctions, filter);

			expect(result).toEqual({
				results: [
					{
						name: 'test-repo-1',
						value: 'test-repo-1',
						url: 'https://github.com/test-owner/test-repo-1',
					},
					{
						name: 'test-repo-2',
						value: 'test-repo-2',
						url: 'https://github.com/test-owner/test-repo-2',
					},
				],
				paginationToken: undefined,
			});
		});

		it('should fetch repositories without filter', async () => {
			const owner = 'test-owner';
			const responseData = {
				items: [
					{ name: 'test-repo-1', html_url: 'https://github.com/test-owner/test-repo-1' },
					{ name: 'test-repo-2', html_url: 'https://github.com/test-owner/test-repo-2' },
				],
				total_count: 2,
			};

			(mockLoadOptionsFunctions.getCurrentNodeParameter as Mock).mockReturnValue(owner);
			(mockLoadOptionsFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
				responseData,
			);

			const result = await getRepositories.call(mockLoadOptionsFunctions);

			expect(result).toEqual({
				results: [
					{
						name: 'test-repo-1',
						value: 'test-repo-1',
						url: 'https://github.com/test-owner/test-repo-1',
					},
					{
						name: 'test-repo-2',
						value: 'test-repo-2',
						url: 'https://github.com/test-owner/test-repo-2',
					},
				],
				paginationToken: undefined,
			});
		});

		it('should use paginationToken when provided', async () => {
			const filter = 'test-repo';
			const paginationToken = '3';
			const owner = 'test-owner';
			const responseData = {
				items: [
					{ name: 'test-repo-5', html_url: 'https://github.com/test-owner/test-repo-5' },
					{ name: 'test-repo-6', html_url: 'https://github.com/test-owner/test-repo-6' },
				],
				total_count: 200,
			};

			(mockLoadOptionsFunctions.getCurrentNodeParameter as Mock).mockReturnValue(owner);
			(mockLoadOptionsFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
				responseData,
			);

			const result = await getRepositories.call(mockLoadOptionsFunctions, filter, paginationToken);

			expect(result).toEqual({
				results: [
					{
						name: 'test-repo-5',
						value: 'test-repo-5',
						url: 'https://github.com/test-owner/test-repo-5',
					},
					{
						name: 'test-repo-6',
						value: 'test-repo-6',
						url: 'https://github.com/test-owner/test-repo-6',
					},
				],
				paginationToken: undefined,
			});

			expect(mockLoadOptionsFunctions.helpers.requestWithAuthentication).toHaveBeenCalledWith(
				'githubOAuth2Api',
				expect.objectContaining({
					method: 'GET',
					qs: expect.objectContaining({ page: 3 }),
				}),
			);
		});

		it('should handle empty repositories', async () => {
			const filter = 'test-repo';
			const owner = 'test-owner';
			const responseData = {
				items: [],
				total_count: 0,
			};

			(mockLoadOptionsFunctions.getCurrentNodeParameter as Mock).mockReturnValue(owner);
			(mockLoadOptionsFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
				responseData,
			);

			const result = await getRepositories.call(mockLoadOptionsFunctions, filter);

			expect(result).toEqual({
				results: [],
				paginationToken: undefined,
			});
		});
	});

	describe('getWorkflows', () => {
		it('should fetch workflows', async () => {
			const owner = 'test-owner';
			const repository = 'test-repo';
			const responseData = {
				workflows: [
					{ id: '1', name: 'workflow-1' },
					{ id: '2', name: 'workflow-2' },
				],
				total_count: 2,
			};

			(mockLoadOptionsFunctions.getCurrentNodeParameter as Mock)
				.mockReturnValueOnce(owner)
				.mockReturnValueOnce(repository);
			(mockLoadOptionsFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
				responseData,
			);

			const result = await getWorkflows.call(mockLoadOptionsFunctions);

			expect(result).toEqual({
				results: [
					{ name: 'workflow-1', value: '1' },
					{ name: 'workflow-2', value: '2' },
				],
				paginationToken: undefined,
			});
		});

		it('should handle pagination', async () => {
			const owner = 'test-owner';
			const repository = 'test-repo';
			const responseData = {
				workflows: [
					{ id: '1', name: 'workflow-1' },
					{ id: '2', name: 'workflow-2' },
				],
				total_count: 200,
			};

			(mockLoadOptionsFunctions.getCurrentNodeParameter as Mock)
				.mockReturnValueOnce(owner)
				.mockReturnValueOnce(repository);
			(mockLoadOptionsFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
				responseData,
			);

			const result = await getWorkflows.call(mockLoadOptionsFunctions);

			expect(result).toEqual({
				results: [
					{ name: 'workflow-1', value: '1' },
					{ name: 'workflow-2', value: '2' },
				],
				paginationToken: '2',
			});
		});

		it('should use paginationToken when provided and return next page token', async () => {
			const paginationToken = '1';
			const owner = 'test-owner';
			const repository = 'test-repo';
			const responseData = {
				workflows: [
					{ id: '3', name: 'workflow-3' },
					{ id: '4', name: 'workflow-4' },
				],
				total_count: 300,
			};

			(mockLoadOptionsFunctions.getCurrentNodeParameter as Mock)
				.mockReturnValueOnce(owner)
				.mockReturnValueOnce(repository);
			(mockLoadOptionsFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
				responseData,
			);

			const result = await getWorkflows.call(mockLoadOptionsFunctions, paginationToken);

			expect(result).toEqual({
				results: [
					{ name: 'workflow-3', value: '3' },
					{ name: 'workflow-4', value: '4' },
				],
				paginationToken: '2',
			});

			expect(mockLoadOptionsFunctions.helpers.requestWithAuthentication).toHaveBeenCalledWith(
				'githubOAuth2Api',
				expect.objectContaining({
					method: 'GET',
					qs: expect.objectContaining({ page: 1 }),
				}),
			);
		});

		it('should handle empty workflows', async () => {
			const owner = 'test-owner';
			const repository = 'test-repo';
			const responseData = {
				workflows: [],
				total_count: 0,
			};

			(mockLoadOptionsFunctions.getCurrentNodeParameter as Mock)
				.mockReturnValueOnce(owner)
				.mockReturnValueOnce(repository);
			(mockLoadOptionsFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
				responseData,
			);

			const result = await getWorkflows.call(mockLoadOptionsFunctions);

			expect(result).toEqual({
				results: [],
				paginationToken: undefined,
			});
		});
	});

	describe('getRefs', () => {
		it('should fetch branches and tags using git/refs endpoint', async () => {
			const owner = 'test-owner';
			const repository = 'test-repo';
			const refsResponse = [
				{ ref: 'refs/heads/Main' },
				{ ref: 'refs/heads/Dev' },
				{ ref: 'refs/tags/v1.0.0' },
				{ ref: 'refs/tags/v2.0.0' },
				{ ref: 'refs/Pull/123/head' },
			];

			(mockLoadOptionsFunctions.getCurrentNodeParameter as Mock).mockImplementation(
				(param: string) => {
					if (param === 'owner') return owner;
					if (param === 'repository') return repository;
				},
			);

			(mockLoadOptionsFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
				refsResponse,
			);

			const result = await getRefs.call(mockLoadOptionsFunctions);

			expect(result).toEqual({
				results: [
					{ name: 'Main', value: 'Main', description: 'Branch: Main' },
					{ name: 'Dev', value: 'Dev', description: 'Branch: Dev' },
					{ name: 'v1.0.0', value: 'v1.0.0', description: 'Tag: v1.0.0' },
					{ name: 'v2.0.0', value: 'v2.0.0', description: 'Tag: v2.0.0' },
					{ name: '123/head', value: '123/head', description: 'Pull: 123/head' },
				],
				paginationToken: undefined,
			});
		});

		it('should use paginationToken when provided', async () => {
			const paginationToken = '3';
			const owner = 'test-owner';
			const repository = 'test-repo';
			const refsResponse = [{ ref: 'refs/heads/branch-5' }, { ref: 'refs/heads/branch-6' }];

			(mockLoadOptionsFunctions.getCurrentNodeParameter as Mock).mockImplementation(
				(param: string) => {
					if (param === 'owner') return owner;
					if (param === 'repository') return repository;
				},
			);

			(mockLoadOptionsFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
				refsResponse,
			);

			const result = await getRefs.call(mockLoadOptionsFunctions, undefined, paginationToken);

			expect(result).toEqual({
				results: [
					{ name: 'branch-5', value: 'branch-5', description: 'Branch: branch-5' },
					{ name: 'branch-6', value: 'branch-6', description: 'Branch: branch-6' },
				],
				paginationToken: undefined,
			});

			expect(mockLoadOptionsFunctions.helpers.requestWithAuthentication).toHaveBeenCalledWith(
				'githubOAuth2Api',
				expect.objectContaining({
					method: 'GET',
					qs: expect.objectContaining({ page: 3 }),
				}),
			);
		});

		it('should filter refs based on the provided filter', async () => {
			const owner = 'test-owner';
			const repository = 'test-repo';
			const refsResponse = [
				{ ref: 'refs/heads/main' },
				{ ref: 'refs/heads/dev' },
				{ ref: 'refs/tags/v1.0.0' },
				{ ref: 'refs/tags/v2.0.0' },
			];

			(mockLoadOptionsFunctions.getCurrentNodeParameter as Mock).mockImplementation(
				(param: string) => {
					if (param === 'owner') return owner;
					if (param === 'repository') return repository;
				},
			);

			(mockLoadOptionsFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
				refsResponse,
			);

			const result = await getRefs.call(mockLoadOptionsFunctions, 'v1');

			expect(result).toEqual({
				results: [{ name: 'v1.0.0', value: 'v1.0.0', description: 'Tag: v1.0.0' }],
			});
		});

		it('should handle pagination correctly', async () => {
			const owner = 'test-owner';
			const repository = 'test-repo';
			const refsResponse = Array(100)
				.fill(0)
				.map((_, i) => ({
					ref: i % 2 === 0 ? `refs/heads/branch-${i}` : `refs/tags/tag-${i}`,
				}));

			(mockLoadOptionsFunctions.getCurrentNodeParameter as Mock).mockImplementation(
				(param: string) => {
					if (param === 'owner') return owner;
					if (param === 'repository') return repository;
				},
			);

			(mockLoadOptionsFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
				refsResponse,
			);

			const result = await getRefs.call(mockLoadOptionsFunctions);

			expect(result.paginationToken).toBe('2');
			expect(result.results.length).toBe(100);
		});
	});
});
