/**
 * Tests for PlayerPrivacyPanel.
 * @module tests/unit/ui/player/PlayerPrivacyPanel.test
 */

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { PlayerPrivacyPanel } from '../../../../src/ui/player/PlayerPrivacyPanel.js';
import { createPrivacySettings } from '../../../../src/contracts/privacy-settings.js';

// Test helper: create a mock FoundryAdapter surface
function createMockAdapter(overrides = {}) {
  return {
    users: {
      get: vi.fn(() => null),
      all: vi.fn(() => []),
      current: vi.fn(() => ({ id: 'test-user' })),
      ...overrides.users,
    },
    notifications: {
      info: vi.fn(),
      warn: vi.fn(),
      error: vi.fn(),
      ...overrides.notifications,
    },
    i18n: {
      localize: vi.fn((key) => {
        // Simple mock for i18n - return known strings or the key
        const translations = {
          'SCRYING_POOL.PrivacyPanel.title': 'Player Privacy Panel',
          'SCRYING_POOL.PrivacyPanel.sectionHeader': 'Automation Opt-ins',
          'SCRYING_POOL.PrivacyPanel.sectionDescription': 'Control which automation effects can affect your camera.',
          'SCRYING_POOL.PrivacyPanel.reactionCamLabel': 'Reaction Cam',
          'SCRYING_POOL.PrivacyPanel.reactionCamDescription': 'Automatically show your camera during key moments (combat, rolls, etc.)',
          'SCRYING_POOL.PrivacyPanel.toggleOn': 'Enabled',
          'SCRYING_POOL.PrivacyPanel.toggleOff': 'Disabled',
          'SCRYING_POOL.PrivacyPanel.readOnlyNotice': 'This player\'s privacy settings are read-only',
          'SCRYING_POOL.PrivacyPanel.savedNotification': 'Privacy settings saved',
          'SCRYING_POOL.PrivacyPanel.saveError': 'Failed to save privacy settings',
        };
        return translations[key] ?? key;
      }),
      ...overrides.i18n,
    },
    ...overrides,
  };
}

// Test helper: create a mock PlayerPrivacyManager surface
function createMockPlayerPrivacyManager(overrides = {}) {
  return {
    getSettings: vi.fn(() => createPrivacySettings()),
    setSetting: vi.fn().mockResolvedValue(undefined),
    isOptedIn: vi.fn(() => false),
    getAllSettings: vi.fn(() => new Map()),
    onChange: vi.fn(() => vi.fn()), // Returns unsubscribe function
    teardown: vi.fn(),
    ...overrides,
  };
}

describe('PlayerPrivacyPanel', () => {
  let adapter;
  let playerPrivacyManager;
  let panel;
  const targetUserId = 'test-user-1';

  beforeEach(() => {
    adapter = createMockAdapter();
    playerPrivacyManager = createMockPlayerPrivacyManager();
    panel = new PlayerPrivacyPanel(adapter, playerPrivacyManager, targetUserId);
  });

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

  describe('constructor', () => {
    it('should construct with valid arguments', () => {
      expect(() =>
        new PlayerPrivacyPanel(adapter, playerPrivacyManager, targetUserId)
      ).not.toThrow();
    });

    it('should throw TypeError for null adapter', () => {
      expect(() =>
        new PlayerPrivacyPanel(null, playerPrivacyManager, targetUserId)
      ).toThrow(TypeError);
      expect(() =>
        new PlayerPrivacyPanel(null, playerPrivacyManager, targetUserId)
      ).toThrow('adapter argument is required');
    });

    it('should throw TypeError for non-object adapter', () => {
      expect(() =>
        new PlayerPrivacyPanel('not an object', playerPrivacyManager, targetUserId)
      ).toThrow(TypeError);
      expect(() =>
        new PlayerPrivacyPanel('not an object', playerPrivacyManager, targetUserId)
      ).toThrow('adapter argument is required');
    });

    it('should throw TypeError for null playerPrivacyManager', () => {
      expect(() =>
        new PlayerPrivacyPanel(adapter, null, targetUserId)
      ).toThrow(TypeError);
      expect(() =>
        new PlayerPrivacyPanel(adapter, null, targetUserId)
      ).toThrow('playerPrivacyManager argument is required');
    });

    it('should throw TypeError for empty targetUserId', () => {
      expect(() =>
        new PlayerPrivacyPanel(adapter, playerPrivacyManager, '')
      ).toThrow(TypeError);
      expect(() =>
        new PlayerPrivacyPanel(adapter, playerPrivacyManager, '')
      ).toThrow('targetUserId must be a non-empty string');
    });

    it('should store dependencies', () => {
      expect(panel._adapter).toBe(adapter);
      expect(panel._playerPrivacyManager).toBe(playerPrivacyManager);
      expect(panel._targetUserId).toBe(targetUserId);
    });
  });

  describe('_prepareContext', () => {
    it('should return context with settings', async () => {
      const settings = createPrivacySettings({
        reactionCamEnabled: true,
      });
      playerPrivacyManager.getSettings.mockReturnValue(settings);
      adapter.users.current.mockReturnValue({ id: targetUserId });

      const context = await panel._prepareContext();

      expect(context.title).toBe('Player Privacy Panel');
      expect(context.sectionHeader).toBe('Automation Opt-ins');
      expect(context.automationEffects).toBeDefined();
      expect(context.automationEffects).toHaveLength(1);
      expect(context.isReadOnly).toBe(false);
      expect(context.isOwnUser).toBe(true);
    });

    it('should mark as read-only when viewing another user', async () => {
      const otherUserId = 'other-user';
      panel = new PlayerPrivacyPanel(adapter, playerPrivacyManager, otherUserId);
      adapter.users.current.mockReturnValue({ id: targetUserId });

      const context = await panel._prepareContext();

      expect(context.isReadOnly).toBe(true);
      expect(context.isOwnUser).toBe(false);
    });

    it('should include the available automation effect', async () => {
      const context = await panel._prepareContext();

      expect(context.automationEffects).toHaveLength(1);
      expect(context.automationEffects[0].key).toBe('reactionCam');
    });

    it('should reflect current settings in context', async () => {
      const settings = createPrivacySettings({
        reactionCamEnabled: true,
      });
      playerPrivacyManager.getSettings.mockReturnValue(settings);

      const context = await panel._prepareContext();

      expect(context.automationEffects[0].enabled).toBe(true);
    });
  });

  describe('_isReadOnlyMode', () => {
    it('should return false when viewing own user', () => {
      adapter.users.current.mockReturnValue({ id: targetUserId });
      expect(panel._isReadOnlyMode()).toBe(false);
    });

    it('should return true when viewing another user', () => {
      const otherUserId = 'other-user';
      panel = new PlayerPrivacyPanel(adapter, playerPrivacyManager, otherUserId);
      adapter.users.current.mockReturnValue({ id: targetUserId });
      expect(panel._isReadOnlyMode()).toBe(true);
    });

    it('should return true when current user is null', () => {
      adapter.users.current.mockReturnValue(null);
      expect(panel._isReadOnlyMode()).toBe(true);
    });

    it('should return true when current user has no id', () => {
      adapter.users.current.mockReturnValue({});
      expect(panel._isReadOnlyMode()).toBe(true);
    });
  });

  describe('_onToggleChange', () => {
    beforeEach(() => {
      panel._currentSettings = createPrivacySettings();
      playerPrivacyManager.setSetting.mockClear();
    });

    it('should revert change in read-only mode', async () => {
      const otherUserId = 'other-user';
      panel = new PlayerPrivacyPanel(adapter, playerPrivacyManager, otherUserId);
      panel._currentSettings = createPrivacySettings();
      adapter.users.current.mockReturnValue({ id: targetUserId });

      const checkbox = {
        checked: true,
        type: 'checkbox',
        getAttribute: () => 'reactionCamEnabled',
      };

      const event = { target: checkbox, preventDefault: vi.fn(), stopPropagation: vi.fn() };

      await panel._onToggleChange(event);

      // Should revert the change
      expect(checkbox.checked).toBe(false);
      expect(playerPrivacyManager.setSetting).not.toHaveBeenCalled();
    });

    it('should update setting for own user', async () => {
      adapter.users.current.mockReturnValue({ id: targetUserId });
      playerPrivacyManager.setSetting.mockResolvedValue(undefined);

      const checkbox = {
        checked: true,
        type: 'checkbox',
        getAttribute: () => 'reactionCamEnabled',
      };

      const event = { target: checkbox, preventDefault: vi.fn(), stopPropagation: vi.fn() };

      await panel._onToggleChange(event);

      expect(playerPrivacyManager.setSetting).toHaveBeenCalledWith(
        targetUserId,
        'reactionCamEnabled',
        true
      );
    });

    it('should handle missing data-setting attribute', async () => {
      adapter.users.current.mockReturnValue({ id: targetUserId });

      const checkbox = {
        checked: true,
        type: 'checkbox',
        getAttribute: () => null,
      };

      const event = { target: checkbox, preventDefault: vi.fn(), stopPropagation: vi.fn() };

      const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

      await panel._onToggleChange(event);

      expect(consoleWarnSpy).toHaveBeenCalledWith(
        expect.stringContaining('missing data-setting')
      );

      consoleWarnSpy.mockRestore();
    });

    it('should handle non-checkbox target', async () => {
      const checkbox = {
        checked: true,
        type: 'radio', // Not checkbox
        getAttribute: () => 'reactionCamEnabled',
      };

      const event = { target: checkbox, preventDefault: vi.fn(), stopPropagation: vi.fn() };

      await panel._onToggleChange(event);

      expect(playerPrivacyManager.setSetting).not.toHaveBeenCalled();
    });

    it('should show success notification on successful update', async () => {
      adapter.users.current.mockReturnValue({ id: targetUserId });
      playerPrivacyManager.setSetting.mockResolvedValue(undefined);

      const checkbox = {
        checked: true,
        type: 'checkbox',
        getAttribute: () => 'reactionCamEnabled',
      };

      const event = { target: checkbox, preventDefault: vi.fn(), stopPropagation: vi.fn() };

      await panel._onToggleChange(event);

      expect(adapter.notifications.info).toHaveBeenCalledWith(
        'Privacy settings saved'
      );
    });

    it('should revert checkbox and show error on TypeError', async () => {
      adapter.users.current.mockReturnValue({ id: targetUserId });
      playerPrivacyManager.setSetting.mockRejectedValue(
        new TypeError('Invalid key')
      );

      const checkbox = {
        checked: true,
        type: 'checkbox',
        getAttribute: () => 'reactionCamEnabled',
      };

      const event = { target: checkbox, preventDefault: vi.fn(), stopPropagation: vi.fn() };

      await panel._onToggleChange(event);

      // Should revert the change
      expect(checkbox.checked).toBe(false);
      expect(adapter.notifications.error).toHaveBeenCalledWith('Invalid key');
    });

    it('should revert checkbox and show generic error on other errors', async () => {
      adapter.users.current.mockReturnValue({ id: targetUserId });
      playerPrivacyManager.setSetting.mockRejectedValue(
        new Error('Some other error')
      );

      const checkbox = {
        checked: true,
        type: 'checkbox',
        getAttribute: () => 'reactionCamEnabled',
      };

      const event = { target: checkbox, preventDefault: vi.fn(), stopPropagation: vi.fn() };

      await panel._onToggleChange(event);

      // Should revert the change
      expect(checkbox.checked).toBe(false);
      expect(adapter.notifications.error).toHaveBeenCalledWith(
        'Failed to save privacy settings'
      );
    });
  });

  describe('_onClose', () => {
    it('should clear cached elements', () => {
      // Set up some cached values
      panel._reactionCamToggle = document.createElement('div');
      panel._currentSettings = createPrivacySettings();

      panel._onClose();

      expect(panel._reactionCamToggle).toBe(null);
      expect(panel._currentSettings).toBe(null);
    });
  });
});
