import { TestBed } from '@angular/core/testing';
import { Location } from '@angular/common';
import { Router } from '@angular/router';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { NavigationStateService } from './navigation-state.service';
import { NavigationContextService } from './navigation-context.service';
import { ReturnUrlContext } from './navigation-context.types';

describe('NavigationStateService', () => {
  let service: NavigationStateService;
  let locationMock: { getState: ReturnType<typeof vi.fn> };
  let routerMock: { navigate: ReturnType<typeof vi.fn> };
  let contextServiceMock: {
    setContext: ReturnType<typeof vi.fn>;
    getContext: ReturnType<typeof vi.fn>;
    getAndClearContext: ReturnType<typeof vi.fn>;
    clearContext: ReturnType<typeof vi.fn>;
    hasContext: ReturnType<typeof vi.fn>;
    clearScope: ReturnType<typeof vi.fn>;
  };

  beforeEach(() => {
    locationMock = {
      getState: vi.fn(),
    };

    routerMock = {
      navigate: vi.fn(),
    };

    contextServiceMock = {
      setContext: vi.fn().mockResolvedValue(undefined),
      getContext: vi.fn().mockResolvedValue(null),
      getAndClearContext: vi.fn().mockResolvedValue(null),
      clearContext: vi.fn().mockResolvedValue(false),
      hasContext: vi.fn().mockResolvedValue(false),
      clearScope: vi.fn().mockResolvedValue(0),
    };

    TestBed.configureTestingModule({
      providers: [
        NavigationStateService,
        { provide: Location, useValue: locationMock },
        { provide: Router, useValue: routerMock },
        { provide: NavigationContextService, useValue: contextServiceMock },
      ],
    });

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

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

  // Context Preservation Methods Tests

  describe('preserveContext', () => {
    it('should call contextService.setContext with correct parameters', async () => {
      const data: ReturnUrlContext = { returnUrl: '/test-page' };
      const scopeKey = 'process-123';

      await service.preserveContext(data, scopeKey);

      expect(contextServiceMock.setContext).toHaveBeenCalledWith(data, scopeKey);
    });

    it('should work without scope key', async () => {
      const data: ReturnUrlContext = { returnUrl: '/test-page' };

      await service.preserveContext(data);

      expect(contextServiceMock.setContext).toHaveBeenCalledWith(data, undefined);
    });
  });

  describe('restoreContext', () => {
    it('should call contextService.getContext with correct parameters', async () => {
      const expectedData: ReturnUrlContext = { returnUrl: '/test-page' };
      contextServiceMock.getContext.mockResolvedValue(expectedData);

      const result = await service.restoreContext<ReturnUrlContext>('scope-123');

      expect(contextServiceMock.getContext).toHaveBeenCalledWith('scope-123');
      expect(result).toEqual(expectedData);
    });

    it('should return null when context not found', async () => {
      contextServiceMock.getContext.mockResolvedValue(null);

      const result = await service.restoreContext();

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

    it('should work without parameters', async () => {
      const expectedData: ReturnUrlContext = { returnUrl: '/test-page' };
      contextServiceMock.getContext.mockResolvedValue(expectedData);

      const result = await service.restoreContext<ReturnUrlContext>();

      expect(contextServiceMock.getContext).toHaveBeenCalledWith(undefined);
      expect(result).toEqual(expectedData);
    });
  });

  describe('restoreAndClearContext', () => {
    it('should call contextService.getAndClearContext with correct parameters', async () => {
      const expectedData: ReturnUrlContext = { returnUrl: '/test-page' };
      contextServiceMock.getAndClearContext.mockResolvedValue(expectedData);

      const result = await service.restoreAndClearContext<ReturnUrlContext>('scope-123');

      expect(contextServiceMock.getAndClearContext).toHaveBeenCalledWith('scope-123');
      expect(result).toEqual(expectedData);
    });

    it('should return null when context not found', async () => {
      contextServiceMock.getAndClearContext.mockResolvedValue(null);

      const result = await service.restoreAndClearContext();

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

  describe('clearPreservedContext', () => {
    it('should call contextService.clearContext and return result', async () => {
      contextServiceMock.clearContext.mockResolvedValue(true);

      const result = await service.clearPreservedContext('scope-123');

      expect(contextServiceMock.clearContext).toHaveBeenCalledWith('scope-123');
      expect(result).toBe(true);
    });

    it('should return false when context not found', async () => {
      contextServiceMock.clearContext.mockResolvedValue(false);

      const result = await service.clearPreservedContext();

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

  describe('hasPreservedContext', () => {
    it('should call contextService.hasContext and return result', async () => {
      contextServiceMock.hasContext.mockResolvedValue(true);

      const result = await service.hasPreservedContext('scope-123');

      expect(contextServiceMock.hasContext).toHaveBeenCalledWith('scope-123');
      expect(result).toBe(true);
    });

    it('should return false when context not found', async () => {
      contextServiceMock.hasContext.mockResolvedValue(false);

      const result = await service.hasPreservedContext();

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

  describe('navigateWithPreservedContext', () => {
    it('should preserve context and navigate', async () => {
      const data: ReturnUrlContext = { returnUrl: '/reward/cart', customerId: 123 };
      const commands = ['/customer/search'];
      const scopeKey = 'process-123';

      routerMock.navigate.mockResolvedValue(true);

      const result = await service.navigateWithPreservedContext(commands, data, scopeKey);

      expect(contextServiceMock.setContext).toHaveBeenCalledWith(data, scopeKey);
      expect(routerMock.navigate).toHaveBeenCalledWith(commands, {
        state: data,
      });
      expect(result).toEqual({ success: true });
    });

    it('should merge navigation extras', async () => {
      const data: ReturnUrlContext = { returnUrl: '/test' };
      const commands = ['/page'];
      const extras = { queryParams: { foo: 'bar' } };

      routerMock.navigate.mockResolvedValue(true);

      await service.navigateWithPreservedContext(commands, data, undefined, extras);

      expect(routerMock.navigate).toHaveBeenCalledWith(commands, {
        queryParams: { foo: 'bar' },
        state: data,
      });
    });

    it('should return false when navigation fails', async () => {
      const data: ReturnUrlContext = { returnUrl: '/test' };
      const commands = ['/page'];

      routerMock.navigate.mockResolvedValue(false);

      const result = await service.navigateWithPreservedContext(commands, data);

      expect(result).toEqual({ success: false });
    });
  });

  describe('clearScopeContexts', () => {
    it('should call contextService.clearScope and return count', async () => {
      contextServiceMock.clearScope.mockResolvedValue(3);

      const result = await service.clearScopeContexts();

      expect(contextServiceMock.clearScope).toHaveBeenCalled();
      expect(result).toBe(3);
    });

    it('should return 0 when no contexts cleared', async () => {
      contextServiceMock.clearScope.mockResolvedValue(0);

      const result = await service.clearScopeContexts();

      expect(result).toBe(0);
    });
  });
});
