/**
 * @fileoverview Tests for ValueObject base class
 */

import { describe, it, expect } from 'vitest';
import { ValueObject } from '../value-object.js';

/**
 * Test implementation of ValueObject
 */
class TestValueObject extends ValueObject<{ value: string; number: number }> {
  constructor(value: string, number: number) {
    super({ value, number });
  }

  get value(): string {
    return this.props.value;
  }

  get number(): number {
    return this.props.number;
  }
}

/**
 * Another test implementation for equality testing
 */
class AnotherValueObject extends ValueObject<{ data: string }> {
  constructor(data: string) {
    super({ data });
  }

  get data(): string {
    return this.props.data;
  }
}

describe('ValueObject', () => {
  describe('constructor', () => {
    it('should create an immutable value object', () => {
      const vo = new TestValueObject('test', 42);

      expect(vo.value).toBe('test');
      expect(vo.number).toBe(42);
    });

    it('should freeze the props object', () => {
      const vo = new TestValueObject('test', 42);

      // @ts-expect-error - Testing immutability
      expect(() => {
        vo.props.value = 'changed';
      }).toThrow();
    });
  });

  describe('equals', () => {
    it('should return true for value objects with the same props', () => {
      const vo1 = new TestValueObject('test', 42);
      const vo2 = new TestValueObject('test', 42);

      expect(vo1.equals(vo2)).toBe(true);
    });

    it('should return false for value objects with different props', () => {
      const vo1 = new TestValueObject('test', 42);
      const vo2 = new TestValueObject('test', 43);

      expect(vo1.equals(vo2)).toBe(false);
    });

    it('should return false for value objects with different property values', () => {
      const vo1 = new TestValueObject('test1', 42);
      const vo2 = new TestValueObject('test2', 42);

      expect(vo1.equals(vo2)).toBe(false);
    });

    it('should handle nested objects in props', () => {
      class NestedValueObject extends ValueObject<{ nested: { a: number; b: string } }> {
        constructor(a: number, b: string) {
          super({ nested: { a, b } });
        }
      }

      const vo1 = new NestedValueObject(1, 'test');
      const vo2 = new NestedValueObject(1, 'test');
      const vo3 = new NestedValueObject(2, 'test');

      expect(vo1.equals(vo2)).toBe(true);
      expect(vo1.equals(vo3)).toBe(false);
    });

    it('should handle arrays in props', () => {
      class ArrayValueObject extends ValueObject<{ items: number[] }> {
        constructor(items: number[]) {
          super({ items });
        }
      }

      const vo1 = new ArrayValueObject([1, 2, 3]);
      const vo2 = new ArrayValueObject([1, 2, 3]);
      const vo3 = new ArrayValueObject([1, 2, 4]);

      expect(vo1.equals(vo2)).toBe(true);
      expect(vo1.equals(vo3)).toBe(false);
    });

    it('should work with different value object types', () => {
      const testVo = new TestValueObject('test', 42);
      const anotherVo = new AnotherValueObject('test');

      // @ts-expect-error - Testing type mismatch
      expect(testVo.equals(anotherVo)).toBe(false);
    });
  });

  describe('immutability', () => {
    it('should not allow modification of props after creation', () => {
      const props = { value: 'test', number: 42 };

      // Create a new value object with the props
      class MutableTestValueObject extends ValueObject<typeof props> {
        getPropsValue(): string {
          return this.props.value;
        }
      }

      const mutableVo = new MutableTestValueObject(props);

      // The props should be frozen, so this should not modify the value object's props
      expect(mutableVo.getPropsValue()).toBe('test');

      // Verify that the internal props are indeed frozen
      expect(Object.isFrozen(mutableVo.props)).toBe(true);
    });

    it('should handle null and undefined values', () => {
      class NullableValueObject extends ValueObject<{ value: string | null; optional?: number }> {
        constructor(value: string | null, optional?: number) {
          super({ value, optional });
        }
      }

      const vo1 = new NullableValueObject(null);
      const vo2 = new NullableValueObject(null, undefined);
      const vo3 = new NullableValueObject('test', 42);

      expect(vo1.equals(vo2)).toBe(true);
      expect(vo1.equals(vo3)).toBe(false);
    });
  });
});
