/**
 * TDD Tests for MediaReference value object
 * 
 * Tests cover:
 * - Discriminated union validation
 * - Type-specific validation
 * - Serialization/deserialization
 * - Equality comparison
 * - Hash generation
 */

import { MediaReference } from '@core/domain/media/MediaReference';

describe('MediaReference', () => {
  describe('System Default Type', () => {
    it('should create system-default reference', () => {
      const ref = MediaReference.createSystemDefault();
      
      expect(ref.type).toBe('system-default');
      expect(ref.variant).toBe('avatar'); // default
    });

    it('should create system-default with custom variant', () => {
      const ref = MediaReference.createSystemDefault('logo');
      
      expect(ref.type).toBe('system-default');
      expect(ref.variant).toBe('logo');
    });

    it('should create system-default with avatar variant', () => {
      const ref = MediaReference.createSystemDefault('avatar', 'male');
      
      expect(ref.type).toBe('system-default');
      expect(ref.variant).toBe('avatar');
      expect(ref.avatarVariant).toBe('male');
    });

    it('should create system-default without avatar variant for logo', () => {
      const ref = MediaReference.createSystemDefault('logo', 'male');
      
      expect(ref.type).toBe('system-default');
      expect(ref.variant).toBe('logo');
      expect(ref.avatarVariant).toBeUndefined();
    });

    it('should validate system-default type', () => {
      const ref = MediaReference.fromJSON({
        type: 'system-default',
        variant: 'avatar'
      });

      expect(ref.type).toBe('system-default');
      expect(ref.variant).toBe('avatar');
    });

    it('should validate system-default with avatar variant', () => {
      const ref = MediaReference.fromJSON({
        type: 'system-default',
        variant: 'avatar',
        avatarVariant: 'female'
      });

      expect(ref.type).toBe('system-default');
      expect(ref.variant).toBe('avatar');
      expect(ref.avatarVariant).toBe('female');
    });

    it('should reject system-default with invalid variant', () => {
      expect(() => {
        MediaReference.fromJSON({
          type: 'system-default',
          variant: 'invalid' as any
        });
      }).toThrow('Invalid variant');
    });

    it('should reject system-default with invalid avatar variant', () => {
      expect(() => {
        MediaReference.fromJSON({
          type: 'system-default',
          variant: 'avatar',
          avatarVariant: 'invalid' as any
        });
      }).toThrow();
    });
  });

  describe('New Static Methods', () => {
    it('should create system-default using systemDefault method', () => {
      const ref = MediaReference.systemDefault('avatar');
      
      expect(ref.type).toBe('system-default');
      expect(ref.variant).toBe('avatar');
    });

    it('should create system-default with avatar variant using systemDefault method', () => {
      const ref = MediaReference.systemDefault('male');
      
      expect(ref.type).toBe('system-default');
      expect(ref.variant).toBe('avatar');
      expect(ref.avatarVariant).toBe('male');
    });

    it('should create generated using generated method', () => {
      const ref = MediaReference.generated('team', 'team-123');
      
      expect(ref.type).toBe('generated');
      expect(ref.generationRequestId).toBe('team-team-123');
    });
  });

  describe('Generated Type', () => {
    it('should create generated reference with request ID', () => {
      const ref = MediaReference.createGenerated('req-123');
      
      expect(ref.type).toBe('generated');
      expect(ref.generationRequestId).toBe('req-123');
    });

    it('should validate generated type', () => {
      const ref = MediaReference.fromJSON({
        type: 'generated',
        generationRequestId: 'req-456'
      });

      expect(ref.type).toBe('generated');
      expect(ref.generationRequestId).toBe('req-456');
    });

    it('should reject generated without request ID', () => {
      expect(() => {
        MediaReference.fromJSON({
          type: 'generated'
        } as any);
      }).toThrow('Generation request ID is required');
    });

    it('should reject generated with empty request ID', () => {
      expect(() => {
        MediaReference.fromJSON({
          type: 'generated',
          generationRequestId: ''
        });
      }).toThrow('Generation request ID is required');
    });
  });

  describe('Uploaded Type', () => {
    it('should create uploaded reference with media ID', () => {
      const ref = MediaReference.createUploaded('media-789');
      
      expect(ref.type).toBe('uploaded');
      expect(ref.mediaId).toBe('media-789');
    });

    it('should validate uploaded type', () => {
      const ref = MediaReference.fromJSON({
        type: 'uploaded',
        mediaId: 'media-abc'
      });

      expect(ref.type).toBe('uploaded');
      expect(ref.mediaId).toBe('media-abc');
    });

    it('should reject uploaded without media ID', () => {
      expect(() => {
        MediaReference.fromJSON({
          type: 'uploaded'
        } as any);
      }).toThrow('Media ID is required');
    });

    it('should reject uploaded with empty media ID', () => {
      expect(() => {
        MediaReference.fromJSON({
          type: 'uploaded',
          mediaId: ''
        });
      }).toThrow('Media ID is required');
    });
  });

  describe('None Type', () => {
    it('should create none reference', () => {
      const ref = MediaReference.createNone();
      
      expect(ref.type).toBe('none');
    });

    it('should validate none type', () => {
      const ref = MediaReference.fromJSON({
        type: 'none'
      });

      expect(ref.type).toBe('none');
    });

    it('should reject none with extra properties', () => {
      expect(() => {
        MediaReference.fromJSON({
          type: 'none',
          mediaId: 'should-not-exist'
        } as any);
      }).toThrow('None type should not have additional properties');
    });
  });

  describe('Invalid Types', () => {
    it('should reject unknown type', () => {
      expect(() => {
        MediaReference.fromJSON({
          type: 'unknown'
        } as any);
      }).toThrow('Invalid type');
    });

    it('should reject missing type', () => {
      expect(() => {
        MediaReference.fromJSON({} as any);
      }).toThrow('Type is required');
    });
  });

  describe('Serialization', () => {
    it('should serialize system-default to JSON', () => {
      const ref = MediaReference.createSystemDefault('logo');
      const json = ref.toJSON();

      expect(json).toEqual({
        type: 'system-default',
        variant: 'logo'
      });
    });

    it('should serialize system-default with avatar variant to JSON', () => {
      const ref = MediaReference.createSystemDefault('avatar', 'female');
      const json = ref.toJSON();

      expect(json).toEqual({
        type: 'system-default',
        variant: 'avatar',
        avatarVariant: 'female'
      });
    });

    it('should deserialize system-default with avatar variant from JSON', () => {
      const json = {
        type: 'system-default',
        variant: 'avatar',
        avatarVariant: 'neutral'
      };
      const ref = MediaReference.fromJSON(json as unknown as Record<string, unknown>);

      expect(ref.type).toBe('system-default');
      expect(ref.variant).toBe('avatar');
      expect(ref.avatarVariant).toBe('neutral');
    });

    it('should serialize generated to JSON', () => {
      const ref = MediaReference.createGenerated('req-123');
      const json = ref.toJSON();

      expect(json).toEqual({
        type: 'generated',
        generationRequestId: 'req-123'
      });
    });

    it('should serialize uploaded to JSON', () => {
      const ref = MediaReference.createUploaded('media-456');
      const json = ref.toJSON();

      expect(json).toEqual({
        type: 'uploaded',
        mediaId: 'media-456'
      });
    });

    it('should serialize none to JSON', () => {
      const ref = MediaReference.createNone();
      const json = ref.toJSON();

      expect(json).toEqual({
        type: 'none'
      });
    });

    it('should deserialize from JSON', () => {
      const json = {
        type: 'uploaded',
        mediaId: 'media-789'
      };
      const ref = MediaReference.fromJSON(json);

      expect(ref.type).toBe('uploaded');
      expect(ref.mediaId).toBe('media-789');
    });
  });

  describe('Equality', () => {
    it('should be equal for same system-default with same variant', () => {
      const ref1 = MediaReference.createSystemDefault('avatar');
      const ref2 = MediaReference.createSystemDefault('avatar');

      expect(ref1.equals(ref2)).toBe(true);
    });

    it('should not be equal for system-default with different variants', () => {
      const ref1 = MediaReference.createSystemDefault('avatar');
      const ref2 = MediaReference.createSystemDefault('logo');

      expect(ref1.equals(ref2)).toBe(false);
    });

    it('should not be equal for system-default with different avatar variants', () => {
      const ref1 = MediaReference.createSystemDefault('avatar', 'male');
      const ref2 = MediaReference.createSystemDefault('avatar', 'female');

      expect(ref1.equals(ref2)).toBe(false);
    });

    it('should be equal for system-default with same avatar variant', () => {
      const ref1 = MediaReference.createSystemDefault('avatar', 'male');
      const ref2 = MediaReference.createSystemDefault('avatar', 'male');

      expect(ref1.equals(ref2)).toBe(true);
    });

    it('should not be equal for system-default with and without avatar variant', () => {
      const ref1 = MediaReference.createSystemDefault('avatar', 'male');
      const ref2 = MediaReference.createSystemDefault('avatar');

      expect(ref1.equals(ref2)).toBe(false);
    });

    it('should be equal for same generated with same request ID', () => {
      const ref1 = MediaReference.createGenerated('req-123');
      const ref2 = MediaReference.createGenerated('req-123');

      expect(ref1.equals(ref2)).toBe(true);
    });

    it('should not be equal for generated with different request IDs', () => {
      const ref1 = MediaReference.createGenerated('req-123');
      const ref2 = MediaReference.createGenerated('req-456');

      expect(ref1.equals(ref2)).toBe(false);
    });

    it('should be equal for same uploaded with same media ID', () => {
      const ref1 = MediaReference.createUploaded('media-123');
      const ref2 = MediaReference.createUploaded('media-123');

      expect(ref1.equals(ref2)).toBe(true);
    });

    it('should not be equal for uploaded with different media IDs', () => {
      const ref1 = MediaReference.createUploaded('media-123');
      const ref2 = MediaReference.createUploaded('media-456');

      expect(ref1.equals(ref2)).toBe(false);
    });

    it('should be equal for none references', () => {
      const ref1 = MediaReference.createNone();
      const ref2 = MediaReference.createNone();

      expect(ref1.equals(ref2)).toBe(true);
    });

    it('should not be equal for different types', () => {
      const ref1 = MediaReference.createSystemDefault();
      const ref2 = MediaReference.createNone();

      expect(ref1.equals(ref2)).toBe(false);
    });

    it('should not be equal to non-MediaReference', () => {
      const ref = MediaReference.createSystemDefault();
      
      expect(ref.equals({} as any)).toBe(false);
      expect(ref.equals(null as any)).toBe(false);
      expect(ref.equals(undefined as any)).toBe(false);
    });
  });

  describe('Hash', () => {
    it('should generate consistent hash for same system-default', () => {
      const ref1 = MediaReference.createSystemDefault('avatar');
      const ref2 = MediaReference.createSystemDefault('avatar');

      expect(ref1.hash()).toBe(ref2.hash());
    });

    it('should generate different hash for different variants', () => {
      const ref1 = MediaReference.createSystemDefault('avatar');
      const ref2 = MediaReference.createSystemDefault('logo');

      expect(ref1.hash()).not.toBe(ref2.hash());
    });

    it('should generate different hash for different avatar variants', () => {
      const ref1 = MediaReference.createSystemDefault('avatar', 'male');
      const ref2 = MediaReference.createSystemDefault('avatar', 'female');

      expect(ref1.hash()).not.toBe(ref2.hash());
    });

    it('should generate same hash for same avatar variant', () => {
      const ref1 = MediaReference.createSystemDefault('avatar', 'male');
      const ref2 = MediaReference.createSystemDefault('avatar', 'male');

      expect(ref1.hash()).toBe(ref2.hash());
    });

    it('should generate different hash for system-default with and without avatar variant', () => {
      const ref1 = MediaReference.createSystemDefault('avatar', 'male');
      const ref2 = MediaReference.createSystemDefault('avatar');

      expect(ref1.hash()).not.toBe(ref2.hash());
    });

    it('should generate consistent hash for same generated', () => {
      const ref1 = MediaReference.createGenerated('req-123');
      const ref2 = MediaReference.createGenerated('req-123');

      expect(ref1.hash()).toBe(ref2.hash());
    });

    it('should generate different hash for different request IDs', () => {
      const ref1 = MediaReference.createGenerated('req-123');
      const ref2 = MediaReference.createGenerated('req-456');

      expect(ref1.hash()).not.toBe(ref2.hash());
    });

    it('should generate consistent hash for same uploaded', () => {
      const ref1 = MediaReference.createUploaded('media-123');
      const ref2 = MediaReference.createUploaded('media-123');

      expect(ref1.hash()).toBe(ref2.hash());
    });

    it('should generate different hash for different media IDs', () => {
      const ref1 = MediaReference.createUploaded('media-123');
      const ref2 = MediaReference.createUploaded('media-456');

      expect(ref1.hash()).not.toBe(ref2.hash());
    });

    it('should generate same hash for none references', () => {
      const ref1 = MediaReference.createNone();
      const ref2 = MediaReference.createNone();

      expect(ref1.hash()).toBe(ref2.hash());
    });

    it('should generate different hash for different types', () => {
      const ref1 = MediaReference.createSystemDefault();
      const ref2 = MediaReference.createNone();

      expect(ref1.hash()).not.toBe(ref2.hash());
    });
  });

  describe('Type Guards', () => {
    it('should correctly identify system-default type', () => {
      const ref = MediaReference.createSystemDefault();
      
      expect(ref.type).toBe('system-default');
      expect(ref.generationRequestId).toBeUndefined();
      expect(ref.mediaId).toBeUndefined();
    });

    it('should correctly identify generated type', () => {
      const ref = MediaReference.createGenerated('req-123');
      
      expect(ref.type).toBe('generated');
      expect(ref.generationRequestId).toBe('req-123');
      expect(ref.mediaId).toBeUndefined();
    });

    it('should correctly identify uploaded type', () => {
      const ref = MediaReference.createUploaded('media-123');
      
      expect(ref.type).toBe('uploaded');
      expect(ref.mediaId).toBe('media-123');
      expect(ref.generationRequestId).toBeUndefined();
    });

    it('should correctly identify none type', () => {
      const ref = MediaReference.createNone();
      
      expect(ref.type).toBe('none');
      expect(ref.generationRequestId).toBeUndefined();
      expect(ref.mediaId).toBeUndefined();
      expect(ref.variant).toBeUndefined();
    });
  });

  describe('Edge Cases', () => {
    it('should handle whitespace in IDs', () => {
      const ref = MediaReference.createGenerated('  req-123  ');
      
      expect(ref.generationRequestId).toBe('req-123');
    });

    it('should handle special characters in IDs', () => {
      const ref = MediaReference.createUploaded('media-abc-123_XYZ');
      
      expect(ref.mediaId).toBe('media-abc-123_XYZ');
    });

    it('should preserve exact string values', () => {
      const id = 'CaseSensitive-ID_123';
      const ref = MediaReference.createUploaded(id);
      
      expect(ref.mediaId).toBe(id);
    });

    it('should handle JSON round-trip', () => {
      const original = MediaReference.createGenerated('req-999');
      const json = original.toJSON();
      const restored = MediaReference.fromJSON(json as unknown as Record<string, unknown>);

      expect(restored.equals(original)).toBe(true);
    });
  });
});