import { afterEach, beforeEach, describe, expect, it, jest } from '@jest/globals';
import { WebComponentProperty } from '@openvidu-meet/typings';
import { OpenViduMeet } from '../../src/components/OpenViduMeet';
import '../../src/index';

describe('OpenViduMeet WebComponent Attributes', () => {
	let component: OpenViduMeet;

	beforeEach(() => {
		component = document.createElement('openvidu-meet') as OpenViduMeet;
		document.body.appendChild(component);
	});

	afterEach(() => {
		document.body.removeChild(component);
		document.body.innerHTML = '';
	});

	// ==========================================
	// IFRAME SETUP
	// ==========================================
	describe('Iframe Configuration', () => {
		// Verify iframe 'allow' media permissions are set correctly for camera/microphone/display
		it('should render iframe with correct media permissions', () => {
			const iframe = component.shadowRoot?.querySelector('iframe');
			expect(iframe).not.toBeNull();

			const allowAttribute = iframe?.getAttribute('allow');
			expect(allowAttribute).toContain('camera');
			expect(allowAttribute).toContain('microphone');
			expect(allowAttribute).toContain('display-capture');
			expect(allowAttribute).toContain('fullscreen');
			expect(allowAttribute).toContain('autoplay');
			expect(allowAttribute).toContain('compute-pressure');
		});

		// Ensure iframe element is present and is an HTMLIFrameElement in shadow DOM
		it('should have iframe ready in shadow DOM', () => {
			const iframe = component.shadowRoot?.querySelector('iframe');
			expect(iframe).toBeInstanceOf(HTMLIFrameElement);
		});
	});

	// ==========================================
	// REQUIRED ATTRIBUTES (room-url | recording-url)
	// ==========================================
	describe('Required Attributes', () => {
		// When no room or recording URL is provided, the component must not set the iframe src and should log an error
		it('should reject iframe src when both room-url and recording-url are missing', () => {
			const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

			// Trigger updateIframeSrc manually
			(component as any).updateIframeSrc();

			const iframe = component.shadowRoot?.querySelector('iframe');

			expect(iframe).toBeDefined();
			expect(iframe?.src).toBeFalsy();
			expect(consoleErrorSpy).toHaveBeenCalledWith('The "room-url" or "recording-url" attribute is required.');

			consoleErrorSpy.mockRestore();
		});

		// Setting `room-url` attribute must result in iframe src being set to that URL
		it('should set iframe src when room-url attribute is provided', () => {
			const roomUrl = 'https://example.com/room/testRoom-123';
			component.setAttribute(WebComponentProperty.ROOM_URL, roomUrl);

			(component as any).updateIframeSrc();

			const iframe = component.shadowRoot?.querySelector('iframe');
			expect(iframe?.src).toBe(roomUrl);
		});

		// Setting `recording-url` attribute (without room-url) must set iframe src to recording URL
		it('should set iframe src when recording-url attribute is provided', () => {
			const recordingUrl = 'https://example.com/recordings/recording-abc-123';
			component.setAttribute(WebComponentProperty.RECORDING_URL, recordingUrl);

			(component as any).updateIframeSrc();

			const iframe = component.shadowRoot?.querySelector('iframe');
			expect(iframe?.src).toBe(recordingUrl);
		});

		// If both `room-url` and `recording-url` are present, `room-url` must take precedence
		it('should prefer room-url over recording-url when both are provided', () => {
			const roomUrl = 'https://example.com/room/testRoom-123';
			const recordingUrl = 'https://example.com/recordings/recording-abc-123';

			component.setAttribute(WebComponentProperty.ROOM_URL, roomUrl);
			component.setAttribute(WebComponentProperty.RECORDING_URL, recordingUrl);

			(component as any).updateIframeSrc();

			const iframe = component.shadowRoot?.querySelector('iframe');
			expect(iframe?.src).toBe(roomUrl);
		});

		// Extract and set target origin from `room-url`, propagate to managers
		it('should extract origin from room-url and set as target origin', () => {
			const domain = 'https://example.com';
			const roomUrl = `${domain}/room/testRoom-123?secret=123456`;
			component.setAttribute(WebComponentProperty.ROOM_URL, roomUrl);

			(component as any).updateIframeSrc();

			expect((component as any).targetIframeOrigin).toBe(domain);
			expect((component as any).commandsManager.targetIframeOrigin).toBe(domain);
			expect((component as any).eventsManager.targetIframeOrigin).toBe(domain);
		});

		// Extract and set target origin from `recording-url`, propagate to managers
		it('should extract origin from recording-url and set as target origin', () => {
			const domain = 'https://recordings.example.com';
			const recordingUrl = `${domain}/recordings/recording-abc-123`;
			component.setAttribute(WebComponentProperty.RECORDING_URL, recordingUrl);

			(component as any).updateIframeSrc();

			expect((component as any).targetIframeOrigin).toBe(domain);
			expect((component as any).commandsManager.targetIframeOrigin).toBe(domain);
			expect((component as any).eventsManager.targetIframeOrigin).toBe(domain);
		});

		// Updating the `room-url` attribute should update the iframe's src accordingly
		it('should update iframe src when room-url attribute changes', () => {
			const roomUrl1 = 'https://example.com/room/room-1';
			const roomUrl2 = 'https://example.com/room/room-2';

			component.setAttribute(WebComponentProperty.ROOM_URL, roomUrl1);
			(component as any).updateIframeSrc();

			let iframe = component.shadowRoot?.querySelector('iframe');
			expect(iframe?.src).toBe(roomUrl1);

			component.setAttribute(WebComponentProperty.ROOM_URL, roomUrl2);
			(component as any).updateIframeSrc();

			iframe = component.shadowRoot?.querySelector('iframe');
			expect(iframe?.src).toBe(roomUrl2);
		});
	});

	// ==========================================
	// OPTIONAL ATTRIBUTES AS QUERY PARAMETERS
	// ==========================================
	describe('Optional Attributes as Query Parameters', () => {
		const baseRoomUrl = 'https://example.com/room/testRoom';

		// Add `participant-name` attribute should append it as a query parameter to the iframe URL
		it('should add participant-name as query parameter', () => {
			const participantName = 'John Doe';
			component.setAttribute(WebComponentProperty.ROOM_URL, baseRoomUrl);
			component.setAttribute(WebComponentProperty.PARTICIPANT_NAME, participantName);

			(component as any).updateIframeSrc();

			const iframe = component.shadowRoot?.querySelector('iframe');
			const url = new URL(iframe?.src || '');
			expect(url.searchParams.get(WebComponentProperty.PARTICIPANT_NAME)).toBe(participantName);
		});

		// Add `e2ee-key` attribute should append it as a query parameter to the iframe URL
		it('should add e2ee-key as query parameter', () => {
			const e2eeKey = 'secret-encryption-key-123';
			component.setAttribute(WebComponentProperty.ROOM_URL, baseRoomUrl);
			component.setAttribute(WebComponentProperty.E2EE_KEY, e2eeKey);

			(component as any).updateIframeSrc();

			const iframe = component.shadowRoot?.querySelector('iframe');
			const url = new URL(iframe?.src || '');
			expect(url.searchParams.get(WebComponentProperty.E2EE_KEY)).toBe(e2eeKey);
		});

		// Add `leave-redirect-url` attribute should append it as a query parameter to the iframe URL
		it('should add leave-redirect-url as query parameter', () => {
			const redirectUrl = 'https://example.com/goodbye';
			component.setAttribute(WebComponentProperty.ROOM_URL, baseRoomUrl);
			component.setAttribute(WebComponentProperty.LEAVE_REDIRECT_URL, redirectUrl);

			(component as any).updateIframeSrc();

			const iframe = component.shadowRoot?.querySelector('iframe');
			const url = new URL(iframe?.src || '');
			expect(url.searchParams.get(WebComponentProperty.LEAVE_REDIRECT_URL)).toBe(redirectUrl);
		});

		// Add `show-only-recordings` boolean-like attribute should be passed through as a query string
		it('should add show-only-recordings as query parameter', () => {
			component.setAttribute(WebComponentProperty.ROOM_URL, baseRoomUrl);
			component.setAttribute(WebComponentProperty.SHOW_ONLY_RECORDINGS, 'true');

			(component as any).updateIframeSrc();

			const iframe = component.shadowRoot?.querySelector('iframe');
			const url = new URL(iframe?.src || '');
			expect(url.searchParams.get(WebComponentProperty.SHOW_ONLY_RECORDINGS)).toBe('true');
		});

		// Multiple optional attributes should all be included as query parameters
		it('should add multiple optional attributes as query parameters', () => {
			const participantName = 'Jane Smith';
			const e2eeKey = 'encryption-key-456';
			const redirectUrl = 'https://example.com/thanks';

			component.setAttribute(WebComponentProperty.ROOM_URL, baseRoomUrl);
			component.setAttribute(WebComponentProperty.PARTICIPANT_NAME, participantName);
			component.setAttribute(WebComponentProperty.E2EE_KEY, e2eeKey);
			component.setAttribute(WebComponentProperty.LEAVE_REDIRECT_URL, redirectUrl);
			component.setAttribute(WebComponentProperty.SHOW_ONLY_RECORDINGS, 'false');

			(component as any).updateIframeSrc();

			const iframe = component.shadowRoot?.querySelector('iframe');
			const url = new URL(iframe?.src || '');

			expect(url.searchParams.get(WebComponentProperty.PARTICIPANT_NAME)).toBe(participantName);
			expect(url.searchParams.get(WebComponentProperty.E2EE_KEY)).toBe(e2eeKey);
			expect(url.searchParams.get(WebComponentProperty.LEAVE_REDIRECT_URL)).toBe(redirectUrl);
			expect(url.searchParams.get(WebComponentProperty.SHOW_ONLY_RECORDINGS)).toBe('false');
		});

		// The base `room-url`/`recording-url` should not appear as query parameters
		it('should NOT add room-url or recording-url as query parameters', () => {
			const roomUrl = 'https://example.com/room/testRoom?secret=abc';
			component.setAttribute(WebComponentProperty.ROOM_URL, roomUrl);

			(component as any).updateIframeSrc();

			const iframe = component.shadowRoot?.querySelector('iframe');
			const url = new URL(iframe?.src || '');

			// room-url should not be in query params (it's the base URL)
			expect(url.searchParams.has(WebComponentProperty.ROOM_URL)).toBe(false);
			expect(url.searchParams.has(WebComponentProperty.RECORDING_URL)).toBe(false);
		});

		// Existing query parameters in the provided `room-url` must be preserved and merged with new ones
		it('should preserve existing query parameters in room-url', () => {
			const roomUrl = 'https://example.com/room/testRoom?secret=abc123&role=moderator';
			const participantName = 'Alice';

			component.setAttribute(WebComponentProperty.ROOM_URL, roomUrl);
			component.setAttribute(WebComponentProperty.PARTICIPANT_NAME, participantName);

			(component as any).updateIframeSrc();

			const iframe = component.shadowRoot?.querySelector('iframe');
			const url = new URL(iframe?.src || '');

			// Original query params should be preserved
			expect(url.searchParams.get('secret')).toBe('abc123');
			expect(url.searchParams.get('role')).toBe('moderator');
			// New param should be added
			expect(url.searchParams.get(WebComponentProperty.PARTICIPANT_NAME)).toBe(participantName);
		});
	});

	// ==========================================
	// CUSTOM/UNKNOWN ATTRIBUTES
	// ==========================================
	describe('Custom Attributes as Query Parameters', () => {
		// Arbitrary custom attributes present on the element should be forwarded as query parameters
		it('should add custom attributes as query parameters', () => {
			const baseRoomUrl = 'https://example.com/room/testRoom';
			component.setAttribute(WebComponentProperty.ROOM_URL, baseRoomUrl);
			component.setAttribute('custom-attr', 'custom-value');
			component.setAttribute('another-param', 'another-value');

			(component as any).updateIframeSrc();

			const iframe = component.shadowRoot?.querySelector('iframe');
			const url = new URL(iframe?.src || '');

			expect(url.searchParams.get('custom-attr')).toBe('custom-value');
			expect(url.searchParams.get('another-param')).toBe('another-value');
		});

		// Attribute names containing dashes or special characters should be preserved in query params
		it('should handle attribute names with special characters', () => {
			const baseRoomUrl = 'https://example.com/room/testRoom';
			component.setAttribute(WebComponentProperty.ROOM_URL, baseRoomUrl);
			component.setAttribute('data-test-id', '12345');

			(component as any).updateIframeSrc();

			const iframe = component.shadowRoot?.querySelector('iframe');
			const url = new URL(iframe?.src || '');

			expect(url.searchParams.get('data-test-id')).toBe('12345');
		});
	});

	// ==========================================
	// EDGE CASES
	// ==========================================
	describe('Edge Cases', () => {
		// Empty string attribute values should still be included as query parameters (empty value)
		it('should handle empty string attributes', () => {
			const baseRoomUrl = 'https://example.com/room/testRoom';
			component.setAttribute(WebComponentProperty.ROOM_URL, baseRoomUrl);
			component.setAttribute(WebComponentProperty.PARTICIPANT_NAME, '');

			(component as any).updateIframeSrc();

			const iframe = component.shadowRoot?.querySelector('iframe');
			const url = new URL(iframe?.src || '');

			// Empty string should still be added as query param
			expect(url.searchParams.has(WebComponentProperty.PARTICIPANT_NAME)).toBe(true);
			expect(url.searchParams.get(WebComponentProperty.PARTICIPANT_NAME)).toBe('');
		});

		// Values with spaces and special characters must be encoded and decoded properly in URLSearchParams
		it('should handle special characters in attribute values', () => {
			const baseRoomUrl = 'https://example.com/room/testRoom';
			const specialName = 'User Name With Spaces & Special=Chars';
			component.setAttribute(WebComponentProperty.ROOM_URL, baseRoomUrl);
			component.setAttribute(WebComponentProperty.PARTICIPANT_NAME, specialName);

			(component as any).updateIframeSrc();

			const iframe = component.shadowRoot?.querySelector('iframe');
			const url = new URL(iframe?.src || '');

			// Should be URL-encoded properly
			expect(url.searchParams.get(WebComponentProperty.PARTICIPANT_NAME)).toBe(specialName);
		});

		// Updating attributes after initial render should change the iframe src and include new params
		it('should handle updating attributes after initial render', () => {
			const baseRoomUrl = 'https://example.com/room/testRoom';
			component.setAttribute(WebComponentProperty.ROOM_URL, baseRoomUrl);
			(component as any).updateIframeSrc();

			const initialSrc = component.shadowRoot?.querySelector('iframe')?.src;

			// Update an attribute
			component.setAttribute(WebComponentProperty.PARTICIPANT_NAME, 'Updated Name');
			(component as any).updateIframeSrc();

			const updatedSrc = component.shadowRoot?.querySelector('iframe')?.src;

			expect(initialSrc).not.toBe(updatedSrc);

			const url = new URL(updatedSrc || '');
			expect(url.searchParams.get(WebComponentProperty.PARTICIPANT_NAME)).toBe('Updated Name');
		});

		// An invalid URL should be caught and logged without throwing uncaught exceptions
		it('should handle invalid URL gracefully', () => {
			const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

			// Set an invalid URL
			component.setAttribute(WebComponentProperty.ROOM_URL, 'not-a-valid-url');

			// Call updateIframeSrc directly - it should catch the error and log it
			(component as any).updateIframeSrc();

			// Verify error was logged with the invalid URL
			expect(consoleErrorSpy).toHaveBeenCalledWith('Invalid URL provided: not-a-valid-url', expect.anything());

			consoleErrorSpy.mockRestore();
		});
	});

	// ==========================================
	// INTEGRATION TESTS
	// ==========================================
	describe('Integration Tests', () => {
		// Full scenario: room-url with existing params and several optional attributes should be merged correctly
		it('should handle complete real-world scenario with room-url and multiple attributes', () => {
			const roomUrl = 'https://meet.example.com/room/team-standup?secret=xyz789';
			const participantName = 'John Doe';
			const e2eeKey = 'my-secure-key';
			const redirectUrl = 'https://example.com/dashboard';

			component.setAttribute(WebComponentProperty.ROOM_URL, roomUrl);
			component.setAttribute(WebComponentProperty.PARTICIPANT_NAME, participantName);
			component.setAttribute(WebComponentProperty.E2EE_KEY, e2eeKey);
			component.setAttribute(WebComponentProperty.LEAVE_REDIRECT_URL, redirectUrl);

			(component as any).updateIframeSrc();

			const iframe = component.shadowRoot?.querySelector('iframe');
			const url = new URL(iframe?.src || '');

			// Verify base URL
			expect(url.origin).toBe('https://meet.example.com');
			expect(url.pathname).toBe('/room/team-standup');

			// Verify all query parameters
			expect(url.searchParams.get('secret')).toBe('xyz789');
			expect(url.searchParams.get(WebComponentProperty.PARTICIPANT_NAME)).toBe(participantName);
			expect(url.searchParams.get(WebComponentProperty.E2EE_KEY)).toBe(e2eeKey);
			expect(url.searchParams.get(WebComponentProperty.LEAVE_REDIRECT_URL)).toBe(redirectUrl);

			// Verify origin was set correctly
			expect((component as any).targetIframeOrigin).toBe('https://meet.example.com');
		});

		// Full scenario using `recording-url`: ensure base path and query params are correct and origin set
		it('should handle complete real-world scenario with recording-url', () => {
			const recordingUrl = 'https://recordings.example.com/view/rec-20231115-abc123';
			const participantName = 'Viewer';

			component.setAttribute(WebComponentProperty.RECORDING_URL, recordingUrl);
			component.setAttribute(WebComponentProperty.PARTICIPANT_NAME, participantName);
			component.setAttribute(WebComponentProperty.SHOW_ONLY_RECORDINGS, 'true');

			(component as any).updateIframeSrc();

			const iframe = component.shadowRoot?.querySelector('iframe');
			const url = new URL(iframe?.src || '');

			// Verify base URL
			expect(url.origin).toBe('https://recordings.example.com');
			expect(url.pathname).toBe('/view/rec-20231115-abc123');

			// Verify query parameters
			expect(url.searchParams.get(WebComponentProperty.PARTICIPANT_NAME)).toBe(participantName);
			expect(url.searchParams.get(WebComponentProperty.SHOW_ONLY_RECORDINGS)).toBe('true');

			// Verify origin was set correctly
			expect((component as any).targetIframeOrigin).toBe('https://recordings.example.com');
		});
	});
});
