/**
 * Tests for InstallPromptService
 *
 * Story 3.4: Verify install prompt service behavior
 */

import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { InstallPromptService } from './install-prompt-service';
import { useInstallPromptStore } from '@/lib/store/install-prompt-store';
import type { BeforeInstallPromptEvent } from '@/lib/store/install-prompt-store';

// Mock window.matchMedia
Object.defineProperty(window, 'matchMedia', {
  writable: true,
  value: vi.fn().mockImplementation(query => ({
    matches: query === '(display-mode: standalone)' ? false : false,
    media: query,
    onchange: null,
    addListener: vi.fn(),
    removeListener: vi.fn(),
    addEventListener: vi.fn(),
    removeEventListener: vi.fn(),
    dispatchEvent: vi.fn(),
  })),
});

describe('InstallPromptService', () => {
  let addEventListenerSpy: ReturnType<typeof vi.spyOn>;
  let mockMatchMedia: ReturnType<typeof vi.fn>;

  beforeEach(() => {
    // Reset store state before each test
    useInstallPromptStore.setState({
      isInstallable: false,
      isInstalled: false,
      deferredPrompt: null
    });

    // Reset service initialization flag
    (InstallPromptService as any).initialized = false;

    // Spy on window.addEventListener
    addEventListenerSpy = vi.spyOn(window, 'addEventListener');

    // Mock matchMedia to return false (not standalone)
    mockMatchMedia = window.matchMedia as ReturnType<typeof vi.fn>;
    mockMatchMedia.mockImplementation(query => ({
      matches: false,
      media: query,
      onchange: null,
      addListener: vi.fn(),
      removeListener: vi.fn(),
      addEventListener: vi.fn(),
      removeEventListener: vi.fn(),
      dispatchEvent: vi.fn(),
    }));
  });

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

  describe('initialize', () => {
    it('should not initialize twice', () => {
      InstallPromptService.initialize();
      InstallPromptService.initialize();

      // Should only add listeners once (beforeinstallprompt + appinstalled)
      expect(addEventListenerSpy).toHaveBeenCalledTimes(2);
    });

    it('should add beforeinstallprompt event listener', () => {
      InstallPromptService.initialize();

      expect(addEventListenerSpy).toHaveBeenCalledWith(
        'beforeinstallprompt',
        expect.any(Function)
      );
    });

    it('should add appinstalled event listener', () => {
      InstallPromptService.initialize();

      expect(addEventListenerSpy).toHaveBeenCalledWith(
        'appinstalled',
        expect.any(Function)
      );
    });

    it('should check installed status on initialize', () => {
      const checkSpy = vi.spyOn(InstallPromptService, 'checkInstalledStatus');

      InstallPromptService.initialize();

      expect(checkSpy).toHaveBeenCalled();
    });

    it('should capture beforeinstallprompt event and update store', () => {
      InstallPromptService.initialize();

      // Get the beforeinstallprompt listener callback
      const beforeInstallListener = addEventListenerSpy.mock.calls.find(
        call => call[0] === 'beforeinstallprompt'
      )?.[1] as EventListener;

      // Create a mock event
      const mockEvent = {
        prompt: vi.fn().mockResolvedValue(undefined),
        userChoice: Promise.resolve({ outcome: 'accepted' as const }),
        preventDefault: vi.fn(),
      } as unknown as Event;

      // Trigger the event
      beforeInstallListener(mockEvent);

      // Verify preventDefault was called
      expect(mockEvent.preventDefault).toHaveBeenCalled();

      // Verify store was updated
      expect(useInstallPromptStore.getState().isInstallable).toBe(true);
      expect(useInstallPromptStore.getState().deferredPrompt).toBeDefined();
    });

    it('should set installed to true on appinstalled event', () => {
      InstallPromptService.initialize();

      // Get the appinstalled listener callback
      const appInstalledListener = addEventListenerSpy.mock.calls.find(
        call => call[0] === 'appinstalled'
      )?.[1] as EventListener;

      // Trigger the event
      appInstalledListener(new Event('appinstalled'));

      // Verify store was updated
      expect(useInstallPromptStore.getState().isInstalled).toBe(true);
    });
  });

  describe('checkInstalledStatus', () => {
    it('should set isInstalled to false when not in standalone mode', () => {
      InstallPromptService.checkInstalledStatus();

      expect(useInstallPromptStore.getState().isInstalled).toBe(false);
    });

    it('should set isInstalled to true when in standalone mode (matchMedia)', () => {
      mockMatchMedia.mockImplementation(query => ({
        matches: query === '(display-mode: standalone)' ? true : false,
        media: query,
        onchange: null,
        addListener: vi.fn(),
        removeListener: vi.fn(),
        addEventListener: vi.fn(),
        removeEventListener: vi.fn(),
        dispatchEvent: vi.fn(),
      }));

      InstallPromptService.checkInstalledStatus();

      expect(useInstallPromptStore.getState().isInstalled).toBe(true);
    });

    it('should set isInstalled to true when in standalone mode (iOS)', () => {
      // Mock navigator.standalone for iOS
      Object.defineProperty(window.navigator, 'standalone', {
        writable: true,
        value: true,
      });

      InstallPromptService.checkInstalledStatus();

      expect(useInstallPromptStore.getState().isInstalled).toBe(true);

      // Cleanup
      (window.navigator as any).standalone = undefined;
    });
  });

  describe('promptInstall', () => {
    it('should return false when no deferred prompt exists', async () => {
      const result = await InstallPromptService.promptInstall();

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

    it('should call promptInstall on store', async () => {
      const mockEvent = {
        prompt: vi.fn().mockResolvedValue(undefined),
        userChoice: Promise.resolve({ outcome: 'accepted' as const }),
      } as unknown as BeforeInstallPromptEvent;

      useInstallPromptStore.getState().setDeferredPrompt(mockEvent);

      const storeSpy = vi.spyOn(useInstallPromptStore.getState(), 'promptInstall');

      await InstallPromptService.promptInstall();

      expect(storeSpy).toHaveBeenCalled();
    });
  });

  describe('isInstallable', () => {
    it('should return false when not installable', () => {
      expect(InstallPromptService.isInstallable()).toBe(false);
    });

    it('should return true when installable', () => {
      useInstallPromptStore.getState().setInstallable(true);

      expect(InstallPromptService.isInstallable()).toBe(true);
    });
  });

  describe('isInstalled', () => {
    it('should return false when not installed', () => {
      expect(InstallPromptService.isInstalled()).toBe(false);
    });

    it('should return true when installed', () => {
      useInstallPromptStore.getState().setInstalled(true);

      expect(InstallPromptService.isInstalled()).toBe(true);
    });
  });

  describe('dismissInstall', () => {
    it('should dismiss the install prompt', () => {
      const mockEvent = {
        prompt: vi.fn(),
        userChoice: Promise.resolve({ outcome: 'dismissed' as const }),
      } as unknown as BeforeInstallPromptEvent;

      useInstallPromptStore.getState().setDeferredPrompt(mockEvent);
      expect(useInstallPromptStore.getState().isInstallable).toBe(true);

      InstallPromptService.dismissInstall();

      expect(useInstallPromptStore.getState().isInstallable).toBe(false);
      expect(useInstallPromptStore.getState().deferredPrompt).toBeNull();
    });
  });

  describe('isSupported', () => {
    it('should return true for non-Safari browsers', () => {
      // Mock user agent to be Chrome
      Object.defineProperty(window.navigator, 'userAgent', {
        writable: true,
        value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
      });

      expect(InstallPromptService.isSupported()).toBe(true);
    });

    it('should return false for Safari browsers', () => {
      // Mock user agent to be Safari
      Object.defineProperty(window.navigator, 'userAgent', {
        writable: true,
        value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15',
      });

      expect(InstallPromptService.isSupported()).toBe(false);
    });

    it('should return false for iOS Safari', () => {
      // Mock user agent to be iOS Safari
      Object.defineProperty(window.navigator, 'userAgent', {
        writable: true,
        value: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Mobile/15E148 Safari/604.1',
      });

      expect(InstallPromptService.isSupported()).toBe(false);
    });
  });
});
