import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import {
  generateDeviceId,
  getDeviceId,
  getDeviceType,
  getUserAgent,
  isAndroid,
  isIOS,
  isMobileBrowser,
  isUserAgent,
  parseSafariVersion,
  resetCurrentDeviceId,
} from './device';

let localStorageValue: string | undefined = undefined;

vi.mock('storage-available', () => ({
  default: vi.fn(() => localStorageValue !== undefined),
}));
vi.mock('./storage', () => ({
  loadFromLocalStorage: vi.fn(() => localStorageValue),
  setInLocalStorage: vi.fn(() => localStorageValue !== undefined),
}));

describe('device utils', () => {
  beforeEach(() => {
    resetCurrentDeviceId();
  });
  afterEach(() => {
    vi.resetAllMocks();
  });
  describe('getUserAgent', () => {
    afterEach(() => {
      vi.unstubAllGlobals();
    });

    it('should return the navigator user agent when defined', () => {
      const mockUserAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)';
      vi.stubGlobal('navigator', {
        userAgent: mockUserAgent,
        vendor: '',
      });

      const result = getUserAgent();
      expect(result).toBe(mockUserAgent);
    });

    it('should return the navigator.vendor as a fallback when defined', () => {
      const mockVendor = 'Apple Computer, Inc.';
      vi.stubGlobal('navigator', {
        userAgent: undefined,
        vendor: mockVendor,
      });

      const result = getUserAgent();
      expect(result).toBe(mockVendor);
    });

    it('should return undefined if navigator is not available', () => {
      vi.stubGlobal('navigator', undefined);

      const result = getUserAgent();
      expect(result).toBeUndefined();
    });
  });

  describe('isUserAgent', () => {
    it('should return true if the user agent matches the regex', () => {
      expect(
        isUserAgent(/iPhone/, 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0)')
      ).toBe(true);
    });

    it('should return false if the user agent does not match regex', () => {
      const result = isUserAgent(
        /iPhone/,
        'Mozilla/5.0 (Macintosh; Intel Mac OS X)'
      );
      expect(result).toBe(false);
    });
  });

  describe('isMobileBrowser', () => {
    it('should return true for mobile browser', () => {
      [
        'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0)',
        'Mozilla/5.0 (Linux; Android 10)',
        'Mozilla/5.0 (iPad; CPU OS 14_0)',
        'Mozilla/5.0 (iPod touch; CPU iPhone OS 14_0)',
        'Mozilla/5.0 (BlackBerry; U; BlackBerry 9900)',
        'Opera Mini/7.5',
      ].forEach((userAgent) => {
        expect(isMobileBrowser(userAgent)).toBe(true);
      });
    });

    it('should return false for desktop browsers', () => {
      [
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)',
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
        'Mozilla/5.0 (X11; Linux x86_64)',
      ].forEach((userAgent) => {
        expect(isMobileBrowser(userAgent)).toBe(false);
      });
    });
  });

  describe('isIOS', () => {
    it('should return true for iOS', () => {
      [
        'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0)',
        'Mozilla/5.0 (iPad; CPU OS 14_0)',
        'Mozilla/5.0 (iPod touch; CPU iPhone OS 14_0)',
      ].forEach((userAgent) => {
        expect(isIOS(userAgent)).toBe(true);
      });
    });

    it('should return false for non-iOS devices', () => {
      [
        'Mozilla/5.0 (Linux; Android 10)',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)',
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
      ].forEach((userAgent) => {
        expect(isIOS(userAgent)).toBe(false);
      });
    });
  });

  describe('isAndroid', () => {
    it('should return true for Android', () => {
      [
        'Mozilla/5.0 (Linux; Android 10)',
        'Mozilla/5.0 (Linux; Android 11; Pixel 5)',
        'Mozilla/5.0 (Linux; U; Android 4.4.2)',
      ].forEach((userAgent) => {
        expect(isAndroid(userAgent)).toBe(true);
      });
    });

    it('should return false for non-Android devices', () => {
      [
        'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0)',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)',
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
      ].forEach((userAgent) => {
        expect(isAndroid(userAgent)).toBe(false);
      });
    });
  });

  describe('parseSafariVersion', () => {
    it('should return the Safari version for major.minor.patch format', () => {
      expect(
        parseSafariVersion(
          'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3.1 Safari/605.1.15'
        )
      ).toEqual({ major: 18, minor: 3, patch: 1 });
    });

    it('should return the Safari version for major.minor format', () => {
      expect(
        parseSafariVersion(
          'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15'
        )
      ).toEqual({ major: 18, minor: 0, patch: 0 });
    });

    it('should return null for non-Safari user agent', () => {
      [
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:120.0) Gecko/20100101 Firefox/120.0',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Safari/605.1.15',
      ].forEach((userAgent) => {
        expect(parseSafariVersion(userAgent)).toBeNull();
      });
    });
  });

  describe('generateDeviceId', () => {
    it('should return a device id', () => {
      const deviceId = generateDeviceId();
      expect(deviceId).toBeDefined();
      expect(typeof deviceId).toBe('string');
      expect(deviceId.length).toBeGreaterThan(0);
    });

    it('should return a device id with prefix', () => {
      const prefix = 'test-';
      const deviceId = generateDeviceId(prefix);
      expect(deviceId).toBeDefined();
      expect(deviceId.startsWith(prefix)).toBe(true);
    });

    it('should generate unique device ids', () => {
      const id1 = generateDeviceId();
      const id2 = generateDeviceId();
      expect(id1).not.toBe(id2);
    });
  });

  describe('getDeviceId', () => {
    it('should return a device id', () => {
      const deviceId = getDeviceId();
      expect(deviceId).toBeDefined();
      expect(typeof deviceId).toBe('string');
      expect(deviceId.length).toBeGreaterThan(0);
    });

    it('should return the same device id on subsequent calls', () => {
      localStorageValue = 'test-device-id-1';
      const id1 = getDeviceId();
      localStorageValue = 'test-device-id-2';
      const id2 = getDeviceId();
      expect(id1).toBe('test-device-id-1');
      expect(id1).toBe(id2);
    });
  });

  describe('getDeviceType', () => {
    it('should return "Android" for Android user agents', () => {
      const androidUA = 'Mozilla/5.0 (Linux; Android 10)';
      const result = getDeviceType(androidUA);
      expect(result).toBe('Android');
    });

    it('should return "iOS" for iOS user agents', () => {
      const iosUA = 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0)';
      const result = getDeviceType(iosUA);
      expect(result).toBe('iOS');
    });

    it('should return "Mac" for macOS user agents', () => {
      const macUA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)';
      const result = getDeviceType(macUA);
      expect(result).toBe('Mac');
    });

    it('should return "Windows" for Windows user agents', () => {
      const windowsUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)';
      const result = getDeviceType(windowsUA);
      expect(result).toBe('Windows');
    });

    it('should return "Linux" for Linux user agents', () => {
      const linuxUA = 'Mozilla/5.0 (X11; Linux x86_64)';
      const result = getDeviceType(linuxUA);
      expect(result).toBe('Linux');
    });

    it('should return "Unknown" for empty user agent', () => {
      const result = getDeviceType('');
      expect(result).toBe('Unknown');
    });

    it('should return "Unknown" for unrecognized user agents', () => {
      const unknownUA = 'SomeUnknownBrowser/1.0';
      const result = getDeviceType(unknownUA);
      expect(result).toBe('Unknown');
    });
  });
});
