/**
 * Tests for PrivacySettings contract.
 * @module tests/unit/contracts/privacy-settings.test
 */

import {
  PRIVACY_SETTINGS_DEFAULT,
  PRIVACY_SETTINGS_VERSION,
  PRIVACY_SETTING_KEYS,
  FEATURE_NAME_MAP,
  MAX_PORTRAIT_SIZE,
  VALID_PORTRAIT_FORMATS,
  createPrivacySettings,
  isValidPrivacySettings,
  validateSettingKey,
  validateSettingValue,
  validateFeatureName,
  validatePortraitDataURL,
} from "../../../src/contracts/privacy-settings.js";

import { describe, it, expect } from "vitest";

describe("privacy-settings contract", () => {
  describe("constants", () => {
    it("should export PRIVACY_SETTINGS_VERSION as 1", () => {
      expect(PRIVACY_SETTINGS_VERSION).toBe(1);
    });

    it("should export PRIVACY_SETTINGS_DEFAULT with all false and null portrait", () => {
      expect(PRIVACY_SETTINGS_DEFAULT).toEqual({
        reactionCamEnabled: false,
        customPortraitFallback: null,
      });
    });

    it("should export PRIVACY_SETTING_KEYS as frozen array including portrait fallback", () => {
      expect(PRIVACY_SETTING_KEYS).toEqual([
        "reactionCamEnabled",
        "customPortraitFallback",
      ]);
      expect(Object.isFrozen(PRIVACY_SETTING_KEYS)).toBe(true);
    });

    it("should export FEATURE_NAME_MAP as frozen object", () => {
      expect(FEATURE_NAME_MAP).toEqual({
        reactionCam: "reactionCamEnabled",
      });
      expect(Object.isFrozen(FEATURE_NAME_MAP)).toBe(true);
    });
  });

  describe("createPrivacySettings", () => {
    it("should return default settings when no overrides provided", () => {
      const result = createPrivacySettings();
      expect(result).toEqual(PRIVACY_SETTINGS_DEFAULT);
    });

    it("should merge overrides with defaults", () => {
      const result = createPrivacySettings({ reactionCamEnabled: true });
      expect(result).toEqual({
        reactionCamEnabled: true,
        customPortraitFallback: null,
      });
    });

    it("should allow customPortraitFallback to be overridden", () => {
      const dataURL = "data:image/png;base64,test";
      const result = createPrivacySettings({ customPortraitFallback: dataURL });
      expect(result).toEqual({
        reactionCamEnabled: false,
        customPortraitFallback: dataURL,
      });
    });

    it("should not modify the defaults object", () => {
      const originalDefaults = { ...PRIVACY_SETTINGS_DEFAULT };
      createPrivacySettings({ reactionCamEnabled: true });
      expect(PRIVACY_SETTINGS_DEFAULT).toEqual(originalDefaults);
    });

    it("should ignore extra properties in overrides", () => {
      const result = createPrivacySettings({
        reactionCamEnabled: true,
        extraProp: "should be ignored",
      });
      expect(result).toEqual({
        reactionCamEnabled: true,
        customPortraitFallback: null,
      });
    });
  });

  describe("isValidPrivacySettings", () => {
    it("should accept valid settings with all false", () => {
      const valid = {
        reactionCamEnabled: false,
      };
      expect(isValidPrivacySettings(valid)).toEqual(valid);
    });

    it("should accept valid settings with all true", () => {
      const valid = {
        reactionCamEnabled: true,
      };
      expect(isValidPrivacySettings(valid)).toEqual(valid);
    });

    it("should accept valid settings with mixed values", () => {
      const valid = {
        reactionCamEnabled: true,
      };
      expect(isValidPrivacySettings(valid)).toEqual(valid);
    });

    it("should throw TypeError for null", () => {
      expect(() => isValidPrivacySettings(null)).toThrow(TypeError);
      expect(() => isValidPrivacySettings(null)).toThrow(
        "PrivacySettings: must be an object"
      );
    });

    it("should throw TypeError for undefined", () => {
      expect(() => isValidPrivacySettings(undefined)).toThrow(TypeError);
      expect(() => isValidPrivacySettings(undefined)).toThrow(
        "PrivacySettings: must be an object"
      );
    });

    it("should throw TypeError for string", () => {
      expect(() => isValidPrivacySettings("invalid")).toThrow(TypeError);
      expect(() => isValidPrivacySettings("invalid")).toThrow(
        "PrivacySettings: must be an object"
      );
    });

    it("should throw TypeError for array", () => {
      expect(() => isValidPrivacySettings([])).toThrow(TypeError);
      expect(() => isValidPrivacySettings([])).toThrow(
        "PrivacySettings: must be an object"
      );
    });

    it("should throw TypeError for unknown keys", () => {
      const invalid = {
        reactionCamEnabled: false,
        extraKey: "invalid",
      };
      expect(() => isValidPrivacySettings(invalid)).toThrow(TypeError);
      expect(() => isValidPrivacySettings(invalid)).toThrow("unknown keys");
    });

    it("should throw TypeError when reactionCamEnabled is not boolean", () => {
      const invalid = {
        reactionCamEnabled: "not a boolean",
      };
      expect(() => isValidPrivacySettings(invalid)).toThrow(TypeError);
      expect(() => isValidPrivacySettings(invalid)).toThrow(
        "reactionCamEnabled must be a boolean"
      );
    });

    it("should throw TypeError when reactionCamEnabled is a number", () => {
      const invalid = {
        reactionCamEnabled: 1,
      };
      expect(() => isValidPrivacySettings(invalid)).toThrow(TypeError);
      expect(() => isValidPrivacySettings(invalid)).toThrow(
        "reactionCamEnabled must be a boolean"
      );
    });

    it("should accept settings with only reactionCamEnabled (backward compatible)", () => {
      // Backward compatibility: settings without all keys are accepted
      const valid = {
        reactionCamEnabled: false,
      };
      expect(() => isValidPrivacySettings(valid)).not.toThrow();
    });

    it("should accept empty object (backward compatible)", () => {
      // Backward compatibility: empty object is accepted
      expect(() => isValidPrivacySettings({})).not.toThrow();
    });
  });

  describe("validateSettingKey", () => {
    it("should accept valid key: reactionCamEnabled", () => {
      expect(validateSettingKey("reactionCamEnabled")).toBe("reactionCamEnabled");
    });

    it("should throw TypeError for empty string", () => {
      expect(() => validateSettingKey("")).toThrow(TypeError);
      expect(() => validateSettingKey("")).toThrow(
        "Setting key must be a non-empty string"
      );
    });

    it("should throw TypeError for non-string", () => {
      expect(() => validateSettingKey(123)).toThrow(TypeError);
      expect(() => validateSettingKey(123)).toThrow(
        "Setting key must be a non-empty string"
      );
    });

    it("should throw TypeError for unknown key", () => {
      expect(() => validateSettingKey("unknownKey")).toThrow(TypeError);
      expect(() => validateSettingKey("unknownKey")).toThrow("unknown key");
    });

    it("should throw TypeError for null", () => {
      expect(() => validateSettingKey(null)).toThrow(TypeError);
      expect(() => validateSettingKey(null)).toThrow(
        "Setting key must be a non-empty string"
      );
    });

    it("should throw TypeError for undefined", () => {
      expect(() => validateSettingKey(undefined)).toThrow(TypeError);
      expect(() => validateSettingKey(undefined)).toThrow(
        "Setting key must be a non-empty string"
      );
    });
  });

  describe("validateSettingValue", () => {
    it("should accept true", () => {
      expect(validateSettingValue(true)).toBe(true);
    });

    it("should accept false", () => {
      expect(validateSettingValue(false)).toBe(false);
    });

    it("should throw TypeError for string", () => {
      expect(() => validateSettingValue("true")).toThrow(TypeError);
      expect(() => validateSettingValue("true")).toThrow(
        "value must be a boolean"
      );
    });

    it("should throw TypeError for number", () => {
      expect(() => validateSettingValue(1)).toThrow(TypeError);
      expect(() => validateSettingValue(1)).toThrow(
        "value must be a boolean"
      );
    });

    it("should throw TypeError for null", () => {
      expect(() => validateSettingValue(null)).toThrow(TypeError);
      expect(() => validateSettingValue(null)).toThrow(
        "value must be a boolean"
      );
    });

    it("should throw TypeError for undefined", () => {
      expect(() => validateSettingValue(undefined)).toThrow(TypeError);
      expect(() => validateSettingValue(undefined)).toThrow(
        "value must be a boolean"
      );
    });

    it("should throw TypeError for object", () => {
      expect(() => validateSettingValue({})).toThrow(TypeError);
      expect(() => validateSettingValue({})).toThrow(
        "value must be a boolean"
      );
    });
  });

  describe("validateFeatureName", () => {
    it("should accept valid feature: reactionCam", () => {
      expect(validateFeatureName("reactionCam")).toBe("reactionCam");
    });

    it("should throw TypeError for empty string", () => {
      expect(() => validateFeatureName("")).toThrow(TypeError);
      expect(() => validateFeatureName("")).toThrow(
        "Feature name must be a non-empty string"
      );
    });

    it("should throw TypeError for non-string", () => {
      expect(() => validateFeatureName(123)).toThrow(TypeError);
      expect(() => validateFeatureName(123)).toThrow(
        "Feature name must be a non-empty string"
      );
    });

    it("should throw TypeError for unknown feature", () => {
      expect(() => validateFeatureName("unknownFeature")).toThrow(TypeError);
      expect(() => validateFeatureName("unknownFeature")).toThrow(
        "unknown feature"
      );
    });

    it("should throw TypeError for null", () => {
      expect(() => validateFeatureName(null)).toThrow(TypeError);
      expect(() => validateFeatureName(null)).toThrow(
        "Feature name must be a non-empty string"
      );
    });

    it("should throw TypeError for undefined", () => {
      expect(() => validateFeatureName(undefined)).toThrow(TypeError);
      expect(() => validateFeatureName(undefined)).toThrow(
        "Feature name must be a non-empty string"
      );
    });
  });

  // ==================== PORTRAIT FALLBACK TESTS ====================

  describe("portrait fallback constants", () => {
    it("should export MAX_PORTRAIT_SIZE as 5MB", () => {
      expect(MAX_PORTRAIT_SIZE).toBe(5 * 1024 * 1024);
    });

    it("should export VALID_PORTRAIT_FORMATS with supported MIME types", () => {
      expect(VALID_PORTRAIT_FORMATS).toEqual([
        "image/png",
        "image/jpeg",
        "image/webp",
        "image/gif",
      ]);
    });
  });

  describe("updated PRIVACY_SETTINGS_DEFAULT with portrait fallback", () => {
    it("should include customPortraitFallback key with null default", () => {
      expect(PRIVACY_SETTINGS_DEFAULT).toHaveProperty("customPortraitFallback");
      expect(PRIVACY_SETTINGS_DEFAULT.customPortraitFallback).toBeNull();
    });

    it("should retain existing boolean settings", () => {
      expect(PRIVACY_SETTINGS_DEFAULT.reactionCamEnabled).toBe(false);
    });
  });

  describe("updated PRIVACY_SETTING_KEYS with portrait fallback", () => {
    it("should include customPortraitFallback in keys", () => {
      expect(PRIVACY_SETTING_KEYS).toContain("customPortraitFallback");
    });

    it("should retain existing keys", () => {
      expect(PRIVACY_SETTING_KEYS).toContain("reactionCamEnabled");
    });
  });

  describe("updated createPrivacySettings with portrait fallback", () => {
    it("should include customPortraitFallback in result when not overridden", () => {
      const result = createPrivacySettings();
      expect(result).toHaveProperty("customPortraitFallback");
      expect(result.customPortraitFallback).toBeNull();
    });

    it("should allow customPortraitFallback to be overridden", () => {
      const dataURL = "data:image/png;base64,test";
      const result = createPrivacySettings({ customPortraitFallback: dataURL });
      expect(result.customPortraitFallback).toBe(dataURL);
    });

    it("should ignore extra properties not in PRIVACY_SETTING_KEYS", () => {
      const result = createPrivacySettings({
        reactionCamEnabled: true,
        customPortraitFallback: "data:image/png;base64,test",
        unknownProp: "should be ignored",
      });
      expect(result).not.toHaveProperty("unknownProp");
      expect(result.reactionCamEnabled).toBe(true);
      expect(result.customPortraitFallback).toBe("data:image/png;base64,test");
    });
  });

  describe("updated isValidPrivacySettings with portrait fallback", () => {
    it("should accept valid settings with customPortraitFallback as string", () => {
      const valid = {
        reactionCamEnabled: false,
        customPortraitFallback: "data:image/png;base64,test",
      };
      expect(isValidPrivacySettings(valid)).toEqual(valid);
    });

    it("should accept valid settings with customPortraitFallback as null", () => {
      const valid = {
        reactionCamEnabled: false,
        customPortraitFallback: null,
      };
      expect(isValidPrivacySettings(valid)).toEqual(valid);
    });

    it("should accept valid settings without customPortraitFallback key", () => {
      // Backward compatibility - may not have the key
      const valid = {
        reactionCamEnabled: false,
      };
      // This should still work - null/undefined is acceptable
      expect(() => isValidPrivacySettings(valid)).not.toThrow();
    });

    it("should throw TypeError when customPortraitFallback is not string or null", () => {
      const invalid = {
        reactionCamEnabled: false,
        customPortraitFallback: 123,
      };
      expect(() => isValidPrivacySettings(invalid)).toThrow(TypeError);
      expect(() => isValidPrivacySettings(invalid)).toThrow(
        "customPortraitFallback must be a string or null"
      );
    });

    it("should throw TypeError when customPortraitFallback is a boolean", () => {
      const invalid = {
        reactionCamEnabled: false,
        customPortraitFallback: true,
      };
      expect(() => isValidPrivacySettings(invalid)).toThrow(TypeError);
      expect(() => isValidPrivacySettings(invalid)).toThrow(
        "customPortraitFallback must be a string or null"
      );
    });

    it("should throw TypeError when customPortraitFallback is an object", () => {
      const invalid = {
        reactionCamEnabled: false,
        customPortraitFallback: {},
      };
      expect(() => isValidPrivacySettings(invalid)).toThrow(TypeError);
      expect(() => isValidPrivacySettings(invalid)).toThrow(
        "customPortraitFallback must be a string or null"
      );
    });
  });

  describe("validatePortraitDataURL", () => {
    it("should accept valid PNG DataURL", () => {
      const dataURL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==";
      expect(validatePortraitDataURL(dataURL)).toBe(dataURL);
    });

    it("should accept valid JPEG DataURL", () => {
      const dataURL = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAP//";
      expect(validatePortraitDataURL(dataURL)).toBe(dataURL);
    });

    it("should accept valid WEBP DataURL", () => {
      const dataURL = "data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA=";
      expect(validatePortraitDataURL(dataURL)).toBe(dataURL);
    });

    it("should accept valid GIF DataURL", () => {
      const dataURL = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAkQBADs=";
      expect(validatePortraitDataURL(dataURL)).toBe(dataURL);
    });

    it("should accept null as valid (no custom portrait)", () => {
      expect(validatePortraitDataURL(null)).toBeNull();
    });

    it("should accept undefined as valid (no custom portrait)", () => {
      expect(validatePortraitDataURL(undefined)).toBeUndefined();
    });

    it("should accept empty string as valid (explicitly no portrait)", () => {
      expect(validatePortraitDataURL("")).toBe("");
    });

    it("should throw TypeError for invalid MIME type SVG", () => {
      const dataURL = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==";
      expect(() => validatePortraitDataURL(dataURL)).toThrow(TypeError);
      expect(() => validatePortraitDataURL(dataURL)).toThrow("Unsupported portrait format");
    });

    it("should throw TypeError for invalid MIME type MP4", () => {
      const dataURL = "data:video/mp4;base64,AAAAIGZ0eXBhdmlmAAAA";
      expect(() => validatePortraitDataURL(dataURL)).toThrow(TypeError);
      expect(() => validatePortraitDataURL(dataURL)).toThrow("Invalid DataURL format");
    });

    it("should throw TypeError for malformed DataURL without MIME type", () => {
      const dataURL = "data:,test";
      expect(() => validatePortraitDataURL(dataURL)).toThrow(TypeError);
      expect(() => validatePortraitDataURL(dataURL)).toThrow("Invalid DataURL format");
    });

    it("should throw TypeError for non-DataURL string", () => {
      const notDataURL = "https://example.com/image.png";
      expect(() => validatePortraitDataURL(notDataURL)).toThrow(TypeError);
      expect(() => validatePortraitDataURL(notDataURL)).toThrow("Invalid DataURL format");
    });

    it("should throw TypeError for non-string value", () => {
      expect(() => validatePortraitDataURL(123)).toThrow(TypeError);
      expect(() => validatePortraitDataURL(123)).toThrow("Invalid DataURL");
    });
  });
});
