import { TestBed } from '@angular/core/testing';
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import { signal } from '@angular/core';
import { NavigationContextService } from './navigation-context.service';
import { TabService } from '@isa/core/tabs';
import { ReturnUrlContext } from './navigation-context.types';
import { NAVIGATION_CONTEXT_METADATA_KEY } from './navigation-context.constants';

describe('NavigationContextService', () => {
  let service: NavigationContextService;
  let tabServiceMock: {
    activatedTabId: ReturnType<typeof signal<number | null>>;
    entityMap: ReturnType<typeof vi.fn>;
    patchTabMetadata: ReturnType<typeof vi.fn>;
  };

  beforeEach(() => {
    // Create mock TabService with signals and methods
    tabServiceMock = {
      activatedTabId: signal<number | null>(null),
      entityMap: vi.fn(),
      patchTabMetadata: vi.fn(),
    };

    TestBed.configureTestingModule({
      providers: [
        NavigationContextService,
        { provide: TabService, useValue: tabServiceMock },
      ],
    });

    service = TestBed.inject(NavigationContextService);
  });

  afterEach(() => {
    vi.clearAllMocks();
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  describe('setContext', () => {
    it('should set context in tab metadata', async () => {
      // Arrange
      const tabId = 123;
      tabServiceMock.activatedTabId.set(tabId);
      tabServiceMock.entityMap.mockReturnValue({
        [tabId]: {
          id: tabId,
          name: 'Test Tab',
          metadata: {},
        },
      });

      const data: ReturnUrlContext = { returnUrl: '/test-page' };

      // Act
      await service.setContext(data);

      // Assert
      expect(tabServiceMock.patchTabMetadata).toHaveBeenCalledWith(
        tabId,
        expect.objectContaining({
          [NAVIGATION_CONTEXT_METADATA_KEY]: expect.objectContaining({
            default: expect.objectContaining({
              data,
              createdAt: expect.any(Number),
            }),
          }),
        }),
      );
    });

    it('should set context with custom scope', async () => {
      // Arrange
      const tabId = 123;
      const customScope = 'customer-details';
      tabServiceMock.activatedTabId.set(tabId);
      tabServiceMock.entityMap.mockReturnValue({
        [tabId]: {
          id: tabId,
          name: 'Test Tab',
          metadata: {},
        },
      });

      const data = { customerId: 42 };

      // Act
      await service.setContext(data, customScope);

      // Assert
      expect(tabServiceMock.patchTabMetadata).toHaveBeenCalledWith(
        tabId,
        expect.objectContaining({
          [NAVIGATION_CONTEXT_METADATA_KEY]: expect.objectContaining({
            [customScope]: expect.objectContaining({
              data,
              createdAt: expect.any(Number),
            }),
          }),
        }),
      );
    });

    it('should throw error when no active tab', async () => {
      // Arrange
      tabServiceMock.activatedTabId.set(null);

      // Act & Assert
      await expect(service.setContext({ returnUrl: '/test' })).rejects.toThrow(
        'No active tab - cannot set navigation context',
      );
    });

    it('should merge with existing contexts', async () => {
      // Arrange
      const tabId = 123;
      tabServiceMock.activatedTabId.set(tabId);

      const existingContexts = {
        'existing-scope': {
          data: { existingData: 'value' },
          createdAt: Date.now() - 1000,
        },
      };

      tabServiceMock.entityMap.mockReturnValue({
        [tabId]: {
          id: tabId,
          name: 'Test Tab',
          metadata: {
            [NAVIGATION_CONTEXT_METADATA_KEY]: existingContexts,
          },
        },
      });

      const newData = { returnUrl: '/new-page' };

      // Act
      await service.setContext(newData);

      // Assert
      expect(tabServiceMock.patchTabMetadata).toHaveBeenCalledWith(
        tabId,
        expect.objectContaining({
          [NAVIGATION_CONTEXT_METADATA_KEY]: expect.objectContaining({
            'existing-scope': existingContexts['existing-scope'],
            default: expect.objectContaining({
              data: newData,
            }),
          }),
        }),
      );
    });

    it('should accept TTL parameter for backward compatibility', async () => {
      // Arrange
      const tabId = 123;
      tabServiceMock.activatedTabId.set(tabId);
      tabServiceMock.entityMap.mockReturnValue({
        [tabId]: {
          id: tabId,
          name: 'Test Tab',
          metadata: {},
        },
      });

      // Act
      await service.setContext({ returnUrl: '/test' }, undefined, 60000);

      // Assert - TTL is ignored but method should still work
      expect(tabServiceMock.patchTabMetadata).toHaveBeenCalled();
    });
  });

  describe('getContext', () => {
    it('should return null when no active tab', async () => {
      // Arrange
      tabServiceMock.activatedTabId.set(null);

      // Act
      const result = await service.getContext();

      // Assert
      expect(result).toBeNull();
    });

    it('should return null when context does not exist', async () => {
      // Arrange
      const tabId = 123;
      tabServiceMock.activatedTabId.set(tabId);
      tabServiceMock.entityMap.mockReturnValue({
        [tabId]: {
          id: tabId,
          name: 'Test Tab',
          metadata: {},
        },
      });

      // Act
      const result = await service.getContext();

      // Assert
      expect(result).toBeNull();
    });

    it('should retrieve context from default scope', async () => {
      // Arrange
      const tabId = 123;
      const data: ReturnUrlContext = { returnUrl: '/test-page' };
      tabServiceMock.activatedTabId.set(tabId);
      tabServiceMock.entityMap.mockReturnValue({
        [tabId]: {
          id: tabId,
          name: 'Test Tab',
          metadata: {
            [NAVIGATION_CONTEXT_METADATA_KEY]: {
              default: {
                data,
                createdAt: Date.now(),
              },
            },
          },
        },
      });

      // Act
      const result = await service.getContext<ReturnUrlContext>();

      // Assert
      expect(result).toEqual(data);
    });

    it('should retrieve context from custom scope', async () => {
      // Arrange
      const tabId = 123;
      const customScope = 'checkout-flow';
      const data = { step: 2, productId: 456 };
      tabServiceMock.activatedTabId.set(tabId);
      tabServiceMock.entityMap.mockReturnValue({
        [tabId]: {
          id: tabId,
          name: 'Test Tab',
          metadata: {
            [NAVIGATION_CONTEXT_METADATA_KEY]: {
              [customScope]: {
                data,
                createdAt: Date.now(),
              },
            },
          },
        },
      });

      // Act
      const result = await service.getContext(customScope);

      // Assert
      expect(result).toEqual(data);
    });

    it('should return null when tab not found', async () => {
      // Arrange
      const tabId = 123;
      tabServiceMock.activatedTabId.set(tabId);
      tabServiceMock.entityMap.mockReturnValue({});

      // Act
      const result = await service.getContext();

      // Assert
      expect(result).toBeNull();
    });

    it('should handle invalid metadata gracefully', async () => {
      // Arrange
      const tabId = 123;
      tabServiceMock.activatedTabId.set(tabId);
      tabServiceMock.entityMap.mockReturnValue({
        [tabId]: {
          id: tabId,
          name: 'Test Tab',
          metadata: {
            [NAVIGATION_CONTEXT_METADATA_KEY]: 'invalid', // Invalid type
          },
        },
      });

      // Act
      const result = await service.getContext();

      // Assert
      expect(result).toBeNull();
    });
  });

  describe('getAndClearContext', () => {
    it('should return null when no active tab', async () => {
      // Arrange
      tabServiceMock.activatedTabId.set(null);

      // Act
      const result = await service.getAndClearContext();

      // Assert
      expect(result).toBeNull();
    });

    it('should retrieve and remove context from default scope', async () => {
      // Arrange
      const tabId = 123;
      const data: ReturnUrlContext = { returnUrl: '/test-page' };
      tabServiceMock.activatedTabId.set(tabId);
      tabServiceMock.entityMap.mockReturnValue({
        [tabId]: {
          id: tabId,
          name: 'Test Tab',
          metadata: {
            [NAVIGATION_CONTEXT_METADATA_KEY]: {
              default: {
                data,
                createdAt: Date.now(),
              },
            },
          },
        },
      });

      // Act
      const result = await service.getAndClearContext<ReturnUrlContext>();

      // Assert
      expect(result).toEqual(data);
      expect(tabServiceMock.patchTabMetadata).toHaveBeenCalledWith(tabId, {
        [NAVIGATION_CONTEXT_METADATA_KEY]: {},
      });
    });

    it('should retrieve and remove context from custom scope', async () => {
      // Arrange
      const tabId = 123;
      const customScope = 'wizard-flow';
      const data = { currentStep: 3 };
      const otherScopeData = { otherData: 'value' };

      tabServiceMock.activatedTabId.set(tabId);
      tabServiceMock.entityMap.mockReturnValue({
        [tabId]: {
          id: tabId,
          name: 'Test Tab',
          metadata: {
            [NAVIGATION_CONTEXT_METADATA_KEY]: {
              [customScope]: {
                data,
                createdAt: Date.now(),
              },
              'other-scope': {
                data: otherScopeData,
                createdAt: Date.now(),
              },
            },
          },
        },
      });

      // Act
      const result = await service.getAndClearContext(customScope);

      // Assert
      expect(result).toEqual(data);
      expect(tabServiceMock.patchTabMetadata).toHaveBeenCalledWith(tabId, {
        [NAVIGATION_CONTEXT_METADATA_KEY]: {
          'other-scope': expect.objectContaining({
            data: otherScopeData,
          }),
        },
      });
    });

    it('should return null when context not found', async () => {
      // Arrange
      const tabId = 123;
      tabServiceMock.activatedTabId.set(tabId);
      tabServiceMock.entityMap.mockReturnValue({
        [tabId]: {
          id: tabId,
          name: 'Test Tab',
          metadata: {},
        },
      });

      // Act
      const result = await service.getAndClearContext();

      // Assert
      expect(result).toBeNull();
      expect(tabServiceMock.patchTabMetadata).not.toHaveBeenCalled();
    });
  });

  describe('clearContext', () => {
    it('should return true when context exists and is cleared', async () => {
      // Arrange
      const tabId = 123;
      const data = { returnUrl: '/test' };
      tabServiceMock.activatedTabId.set(tabId);
      tabServiceMock.entityMap.mockReturnValue({
        [tabId]: {
          id: tabId,
          name: 'Test Tab',
          metadata: {
            [NAVIGATION_CONTEXT_METADATA_KEY]: {
              default: { data, createdAt: Date.now() },
            },
          },
        },
      });

      // Act
      const result = await service.clearContext();

      // Assert
      expect(result).toBe(true);
    });

    it('should return false when context not found', async () => {
      // Arrange
      const tabId = 123;
      tabServiceMock.activatedTabId.set(tabId);
      tabServiceMock.entityMap.mockReturnValue({
        [tabId]: {
          id: tabId,
          name: 'Test Tab',
          metadata: {},
        },
      });

      // Act
      const result = await service.clearContext();

      // Assert
      expect(result).toBe(false);
    });
  });

  describe('clearScope', () => {
    it('should clear all contexts for active tab', async () => {
      // Arrange
      const tabId = 123;
      const contexts = {
        default: { data: { url: '/test' }, createdAt: Date.now() },
        'scope-1': { data: { value: 1 }, createdAt: Date.now() },
        'scope-2': { data: { value: 2 }, createdAt: Date.now() },
      };

      tabServiceMock.activatedTabId.set(tabId);
      tabServiceMock.entityMap.mockReturnValue({
        [tabId]: {
          id: tabId,
          name: 'Test Tab',
          metadata: {
            [NAVIGATION_CONTEXT_METADATA_KEY]: contexts,
          },
        },
      });

      // Act
      const clearedCount = await service.clearScope();

      // Assert
      expect(clearedCount).toBe(3);
      expect(tabServiceMock.patchTabMetadata).toHaveBeenCalledWith(tabId, {
        [NAVIGATION_CONTEXT_METADATA_KEY]: {},
      });
    });

    it('should return 0 when no contexts exist', async () => {
      // Arrange
      const tabId = 123;
      tabServiceMock.activatedTabId.set(tabId);
      tabServiceMock.entityMap.mockReturnValue({
        [tabId]: {
          id: tabId,
          name: 'Test Tab',
          metadata: {},
        },
      });

      // Act
      const clearedCount = await service.clearScope();

      // Assert
      expect(clearedCount).toBe(0);
      expect(tabServiceMock.patchTabMetadata).not.toHaveBeenCalled();
    });

    it('should return 0 when no active tab', async () => {
      // Arrange
      tabServiceMock.activatedTabId.set(null);

      // Act
      const clearedCount = await service.clearScope();

      // Assert
      expect(clearedCount).toBe(0);
    });
  });

  describe('clearAll', () => {
    it('should clear all contexts for active tab', async () => {
      // Arrange
      const tabId = 123;
      const contexts = {
        default: { data: { url: '/test' }, createdAt: Date.now() },
      };

      tabServiceMock.activatedTabId.set(tabId);
      tabServiceMock.entityMap.mockReturnValue({
        [tabId]: {
          id: tabId,
          name: 'Test Tab',
          metadata: {
            [NAVIGATION_CONTEXT_METADATA_KEY]: contexts,
          },
        },
      });

      // Act
      await service.clearAll();

      // Assert
      expect(tabServiceMock.patchTabMetadata).toHaveBeenCalledWith(tabId, {
        [NAVIGATION_CONTEXT_METADATA_KEY]: {},
      });
    });
  });

  describe('hasContext', () => {
    it('should return true when context exists', async () => {
      // Arrange
      const tabId = 123;
      const data = { returnUrl: '/test' };
      tabServiceMock.activatedTabId.set(tabId);
      tabServiceMock.entityMap.mockReturnValue({
        [tabId]: {
          id: tabId,
          name: 'Test Tab',
          metadata: {
            [NAVIGATION_CONTEXT_METADATA_KEY]: {
              default: { data, createdAt: Date.now() },
            },
          },
        },
      });

      // Act
      const result = await service.hasContext();

      // Assert
      expect(result).toBe(true);
    });

    it('should return false when context does not exist', async () => {
      // Arrange
      const tabId = 123;
      tabServiceMock.activatedTabId.set(tabId);
      tabServiceMock.entityMap.mockReturnValue({
        [tabId]: {
          id: tabId,
          name: 'Test Tab',
          metadata: {},
        },
      });

      // Act
      const result = await service.hasContext();

      // Assert
      expect(result).toBe(false);
    });

    it('should check custom scope', async () => {
      // Arrange
      const tabId = 123;
      const customScope = 'wizard';
      const data = { step: 1 };
      tabServiceMock.activatedTabId.set(tabId);
      tabServiceMock.entityMap.mockReturnValue({
        [tabId]: {
          id: tabId,
          name: 'Test Tab',
          metadata: {
            [NAVIGATION_CONTEXT_METADATA_KEY]: {
              [customScope]: { data, createdAt: Date.now() },
            },
          },
        },
      });

      // Act
      const result = await service.hasContext(customScope);

      // Assert
      expect(result).toBe(true);
    });
  });

  describe('getContextCount', () => {
    it('should return total number of contexts for active tab', async () => {
      // Arrange
      const tabId = 123;
      const contexts = {
        default: { data: { url: '/test' }, createdAt: Date.now() },
        'scope-1': { data: { value: 1 }, createdAt: Date.now() },
        'scope-2': { data: { value: 2 }, createdAt: Date.now() },
      };

      tabServiceMock.activatedTabId.set(tabId);
      tabServiceMock.entityMap.mockReturnValue({
        [tabId]: {
          id: tabId,
          name: 'Test Tab',
          metadata: {
            [NAVIGATION_CONTEXT_METADATA_KEY]: contexts,
          },
        },
      });

      // Act
      const count = await service.getContextCount();

      // Assert
      expect(count).toBe(3);
    });

    it('should return 0 when no contexts exist', async () => {
      // Arrange
      const tabId = 123;
      tabServiceMock.activatedTabId.set(tabId);
      tabServiceMock.entityMap.mockReturnValue({
        [tabId]: {
          id: tabId,
          name: 'Test Tab',
          metadata: {},
        },
      });

      // Act
      const count = await service.getContextCount();

      // Assert
      expect(count).toBe(0);
    });

    it('should return 0 when no active tab', async () => {
      // Arrange
      tabServiceMock.activatedTabId.set(null);

      // Act
      const count = await service.getContextCount();

      // Assert
      expect(count).toBe(0);
    });
  });
});
