import { describe, it, expect } from 'vitest';
import {
  areAddresseesEqual,
  areBranchesEqual,
  deduplicateAddressees,
  deduplicateBranches,
} from './deduplicate-addressees.helper';
import {
  DisplayAddresseeDTO,
  DisplayBranchDTO,
  AddressDTO,
} from '@generated/swagger/oms-api';

describe('areAddresseesEqual', () => {
  const sampleAddress: AddressDTO = {
    street: 'Teststraße',
    streetNumber: '123',
    city: 'Berlin',
    zipCode: '10115',
    country: 'DEU',
  };

  const createAddressee = (
    firstName: string,
    lastName: string,
    address?: AddressDTO,
  ): DisplayAddresseeDTO => ({
    firstName,
    lastName,
    address,
    gender: 0, // NotSet
  });

  it('should return true when both addressees are undefined', () => {
    // Act
    const result = areAddresseesEqual(undefined, undefined);

    // Assert
    expect(result).toBe(true);
  });

  it('should return false when only first addressee is undefined', () => {
    // Arrange
    const addressee = createAddressee('John', 'Doe', sampleAddress);

    // Act
    const result = areAddresseesEqual(undefined, addressee);

    // Assert
    expect(result).toBe(false);
  });

  it('should return false when only second addressee is undefined', () => {
    // Arrange
    const addressee = createAddressee('John', 'Doe', sampleAddress);

    // Act
    const result = areAddresseesEqual(addressee, undefined);

    // Assert
    expect(result).toBe(false);
  });

  it('should return true when addressees have same name and address', () => {
    // Arrange
    const addressee1 = createAddressee('John', 'Doe', sampleAddress);
    const addressee2 = createAddressee('John', 'Doe', { ...sampleAddress });

    // Act
    const result = areAddresseesEqual(addressee1, addressee2);

    // Assert
    expect(result).toBe(true);
  });

  it('should return false when addressees have different first names', () => {
    // Arrange
    const addressee1 = createAddressee('John', 'Doe', sampleAddress);
    const addressee2 = createAddressee('Jane', 'Doe', sampleAddress);

    // Act
    const result = areAddresseesEqual(addressee1, addressee2);

    // Assert
    expect(result).toBe(false);
  });

  it('should return false when addressees have different last names', () => {
    // Arrange
    const addressee1 = createAddressee('John', 'Doe', sampleAddress);
    const addressee2 = createAddressee('John', 'Smith', sampleAddress);

    // Act
    const result = areAddresseesEqual(addressee1, addressee2);

    // Assert
    expect(result).toBe(false);
  });

  it('should return false when addressees have different addresses', () => {
    // Arrange
    const addressee1 = createAddressee('John', 'Doe', sampleAddress);
    const addressee2 = createAddressee('John', 'Doe', {
      ...sampleAddress,
      street: 'Other Street',
    });

    // Act
    const result = areAddresseesEqual(addressee1, addressee2);

    // Assert
    expect(result).toBe(false);
  });

  it('should return true when both addressees have no address', () => {
    // Arrange
    const addressee1 = createAddressee('John', 'Doe');
    const addressee2 = createAddressee('John', 'Doe');

    // Act
    const result = areAddresseesEqual(addressee1, addressee2);

    // Assert
    expect(result).toBe(true);
  });

  it('should return false when one has address and other does not', () => {
    // Arrange
    const addressee1 = createAddressee('John', 'Doe', sampleAddress);
    const addressee2 = createAddressee('John', 'Doe');

    // Act
    const result = areAddresseesEqual(addressee1, addressee2);

    // Assert
    expect(result).toBe(false);
  });

  it('should handle empty string names', () => {
    // Arrange
    const addressee1 = createAddressee('', '', sampleAddress);
    const addressee2 = createAddressee('', '', sampleAddress);

    // Act
    const result = areAddresseesEqual(addressee1, addressee2);

    // Assert
    expect(result).toBe(true);
  });

  it('should treat undefined names as empty strings', () => {
    // Arrange
    const addressee1: DisplayAddresseeDTO = {
      gender: 0, // NotSet
      address: sampleAddress,
    };
    const addressee2: DisplayAddresseeDTO = {
      firstName: '',
      lastName: '',
      gender: 0, // NotSet
      address: sampleAddress,
    };

    // Act
    const result = areAddresseesEqual(addressee1, addressee2);

    // Assert
    expect(result).toBe(true);
  });
});

describe('deduplicateAddressees', () => {
  const sampleAddress: AddressDTO = {
    street: 'Teststraße',
    streetNumber: '123',
    city: 'Berlin',
    zipCode: '10115',
    country: 'DEU',
  };

  const createAddressee = (
    firstName: string,
    lastName: string,
    address?: AddressDTO,
  ): DisplayAddresseeDTO => ({
    firstName,
    lastName,
    address,
    gender: 0, // NotSet
  });

  it('should return empty array when input is empty', () => {
    // Arrange
    const addressees: DisplayAddresseeDTO[] = [];

    // Act
    const result = deduplicateAddressees(addressees);

    // Assert
    expect(result).toEqual([]);
  });

  it('should filter out undefined values', () => {
    // Arrange
    const addressee = createAddressee('John', 'Doe', sampleAddress);
    const addressees = [undefined, addressee, undefined];

    // Act
    const result = deduplicateAddressees(addressees);

    // Assert
    expect(result).toHaveLength(1);
    expect(result[0]).toEqual(addressee);
  });

  it('should return single item when array has one addressee', () => {
    // Arrange
    const addressee = createAddressee('John', 'Doe', sampleAddress);
    const addressees = [addressee];

    // Act
    const result = deduplicateAddressees(addressees);

    // Assert
    expect(result).toHaveLength(1);
    expect(result[0]).toEqual(addressee);
  });

  it('should return all items when no duplicates exist', () => {
    // Arrange
    const addressees = [
      createAddressee('John', 'Doe', sampleAddress),
      createAddressee('Jane', 'Smith', sampleAddress),
      createAddressee('Bob', 'Johnson', {
        ...sampleAddress,
        street: 'Other Street',
      }),
    ];

    // Act
    const result = deduplicateAddressees(addressees);

    // Assert
    expect(result).toHaveLength(3);
  });

  it('should remove duplicate addressees keeping first occurrence', () => {
    // Arrange
    const addressee1 = createAddressee('John', 'Doe', sampleAddress);
    const addressee2 = createAddressee('John', 'Doe', { ...sampleAddress });
    const addressees = [addressee1, addressee2];

    // Act
    const result = deduplicateAddressees(addressees);

    // Assert
    expect(result).toHaveLength(1);
    expect(result[0]).toBe(addressee1); // First occurrence kept
  });

  it('should handle multiple duplicates and keep only first', () => {
    // Arrange
    const addressee1 = createAddressee('John', 'Doe', sampleAddress);
    const addressee2 = createAddressee('John', 'Doe', { ...sampleAddress });
    const addressee3 = createAddressee('John', 'Doe', { ...sampleAddress });
    const addressees = [addressee1, addressee2, addressee3];

    // Act
    const result = deduplicateAddressees(addressees);

    // Assert
    expect(result).toHaveLength(1);
    expect(result[0]).toBe(addressee1);
  });

  it('should not consider different names but same address as duplicates', () => {
    // Arrange
    const addressees = [
      createAddressee('John', 'Doe', sampleAddress),
      createAddressee('Jane', 'Doe', sampleAddress),
    ];

    // Act
    const result = deduplicateAddressees(addressees);

    // Assert
    expect(result).toHaveLength(2);
  });

  it('should not consider same names but different addresses as duplicates', () => {
    // Arrange
    const addressees = [
      createAddressee('John', 'Doe', sampleAddress),
      createAddressee('John', 'Doe', {
        ...sampleAddress,
        street: 'Other Street',
      }),
    ];

    // Act
    const result = deduplicateAddressees(addressees);

    // Assert
    expect(result).toHaveLength(2);
  });

  it('should handle complex scenario with mixed duplicates and unique items', () => {
    // Arrange
    const addressee1 = createAddressee('John', 'Doe', sampleAddress);
    const addressee2 = createAddressee('John', 'Doe', { ...sampleAddress }); // Duplicate of 1
    const addressee3 = createAddressee('Jane', 'Smith', sampleAddress);
    const addressee4 = createAddressee('Jane', 'Smith', { ...sampleAddress }); // Duplicate of 3
    const addressee5 = createAddressee('Bob', 'Johnson', sampleAddress);
    const addressees = [
      addressee1,
      addressee2,
      addressee3,
      addressee4,
      addressee5,
      undefined,
    ];

    // Act
    const result = deduplicateAddressees(addressees);

    // Assert
    expect(result).toHaveLength(3);
    expect(result[0]).toBe(addressee1);
    expect(result[1]).toBe(addressee3);
    expect(result[2]).toBe(addressee5);
  });

  it('should preserve order of first occurrences', () => {
    // Arrange
    const addressee1 = createAddressee('Alice', 'Wonder', sampleAddress);
    const addressee2 = createAddressee('Bob', 'Builder', sampleAddress);
    const addressee3 = createAddressee('Alice', 'Wonder', { ...sampleAddress }); // Duplicate
    const addressees = [addressee1, addressee2, addressee3];

    // Act
    const result = deduplicateAddressees(addressees);

    // Assert
    expect(result).toHaveLength(2);
    expect(result[0]).toBe(addressee1);
    expect(result[1]).toBe(addressee2);
  });
});

describe('areBranchesEqual', () => {
  const sampleAddress: AddressDTO = {
    street: 'Teststraße',
    streetNumber: '123',
    city: 'Berlin',
    zipCode: '10115',
    country: 'DEU',
  };

  const createBranch = (
    name: string,
    address?: AddressDTO,
  ): DisplayBranchDTO => ({
    name,
    address,
  });

  it('should return true when both branches are undefined', () => {
    // Act
    const result = areBranchesEqual(undefined, undefined);

    // Assert
    expect(result).toBe(true);
  });

  it('should return false when only first branch is undefined', () => {
    // Arrange
    const branch = createBranch('Branch 1', sampleAddress);

    // Act
    const result = areBranchesEqual(undefined, branch);

    // Assert
    expect(result).toBe(false);
  });

  it('should return true when branches have same name and address', () => {
    // Arrange
    const branch1 = createBranch('Branch 1', sampleAddress);
    const branch2 = createBranch('Branch 1', { ...sampleAddress });

    // Act
    const result = areBranchesEqual(branch1, branch2);

    // Assert
    expect(result).toBe(true);
  });

  it('should return false when branches have different names', () => {
    // Arrange
    const branch1 = createBranch('Branch 1', sampleAddress);
    const branch2 = createBranch('Branch 2', sampleAddress);

    // Act
    const result = areBranchesEqual(branch1, branch2);

    // Assert
    expect(result).toBe(false);
  });

  it('should return false when branches have different addresses', () => {
    // Arrange
    const branch1 = createBranch('Branch 1', sampleAddress);
    const branch2 = createBranch('Branch 1', {
      ...sampleAddress,
      street: 'Other Street',
    });

    // Act
    const result = areBranchesEqual(branch1, branch2);

    // Assert
    expect(result).toBe(false);
  });
});

describe('deduplicateBranches', () => {
  const sampleAddress: AddressDTO = {
    street: 'Teststraße',
    streetNumber: '123',
    city: 'Berlin',
    zipCode: '10115',
    country: 'DEU',
  };

  const createBranch = (
    name: string,
    address?: AddressDTO,
  ): DisplayBranchDTO => ({
    name,
    address,
  });

  it('should return empty array when input is empty', () => {
    // Arrange
    const branches: DisplayBranchDTO[] = [];

    // Act
    const result = deduplicateBranches(branches);

    // Assert
    expect(result).toEqual([]);
  });

  it('should filter out undefined values', () => {
    // Arrange
    const branch = createBranch('Branch 1', sampleAddress);
    const branches = [undefined, branch, undefined];

    // Act
    const result = deduplicateBranches(branches);

    // Assert
    expect(result).toHaveLength(1);
    expect(result[0]).toEqual(branch);
  });

  it('should remove duplicate branches keeping first occurrence', () => {
    // Arrange
    const branch1 = createBranch('Branch 1', sampleAddress);
    const branch2 = createBranch('Branch 1', { ...sampleAddress });
    const branches = [branch1, branch2];

    // Act
    const result = deduplicateBranches(branches);

    // Assert
    expect(result).toHaveLength(1);
    expect(result[0]).toBe(branch1);
  });

  it('should return all items when no duplicates exist', () => {
    // Arrange
    const branches = [
      createBranch('Branch 1', sampleAddress),
      createBranch('Branch 2', sampleAddress),
      createBranch('Branch 3', {
        ...sampleAddress,
        street: 'Other Street',
      }),
    ];

    // Act
    const result = deduplicateBranches(branches);

    // Assert
    expect(result).toHaveLength(3);
  });
});
