import { validate } from 'class-validator';
import { IsVietnamDistrict } from '../is-vietnam-district.validator';
import { IsVietnamPhone } from '../is-vietnam-phone.validator';
import { IsVND } from '../is-vnd.validator';

// Test DTOs
class PhoneDto {
  @IsVietnamPhone()
  phone!: string;
}

class DistrictDto {
  @IsVietnamDistrict()
  district!: string;
}

class AmountDto {
  @IsVND()
  amount!: number;
}

class CustomAmountDto {
  @IsVND({ min: 10_000, max: 50_000_000 })
  amount!: number;
}

function makeDto<T extends object>(Cls: new () => T, overrides: Partial<T>): T {
  const dto = new Cls();
  Object.assign(dto, overrides);
  return dto;
}

describe('IsVietnamPhone', () => {
  it('accepts valid phone with 0 prefix', async () => {
    const dto = makeDto(PhoneDto, { phone: '0912345678' });
    const errors = await validate(dto);
    expect(errors).toHaveLength(0);
  });

  it('accepts valid phone with +84 prefix', async () => {
    const dto = makeDto(PhoneDto, { phone: '+84912345678' });
    const errors = await validate(dto);
    expect(errors).toHaveLength(0);
  });

  it('accepts valid phone with 84 prefix', async () => {
    const dto = makeDto(PhoneDto, { phone: '84912345678' });
    const errors = await validate(dto);
    expect(errors).toHaveLength(0);
  });

  it('accepts phone with spaces/dashes', async () => {
    const dto = makeDto(PhoneDto, { phone: '091 234 5678' });
    const errors = await validate(dto);
    expect(errors).toHaveLength(0);
  });

  it('rejects invalid phone number', async () => {
    const dto = makeDto(PhoneDto, { phone: '12345' });
    const errors = await validate(dto);
    expect(errors).toHaveLength(1);
    expect(errors[0].constraints).toHaveProperty('isVietnamPhone');
  });

  it('rejects non-string value', async () => {
    const dto = makeDto(PhoneDto, { phone: 12345 as unknown as string });
    const errors = await validate(dto);
    expect(errors).toHaveLength(1);
  });

  it('rejects empty string', async () => {
    const dto = makeDto(PhoneDto, { phone: '' });
    const errors = await validate(dto);
    expect(errors).toHaveLength(1);
  });
});

describe('IsVietnamDistrict', () => {
  it('accepts "Quận 1"', async () => {
    const dto = makeDto(DistrictDto, { district: 'Quận 1' });
    const errors = await validate(dto);
    expect(errors).toHaveLength(0);
  });

  it('accepts "Huyện Củ Chi"', async () => {
    const dto = makeDto(DistrictDto, { district: 'Huyện Củ Chi' });
    const errors = await validate(dto);
    expect(errors).toHaveLength(0);
  });

  it('accepts "Bình Thạnh"', async () => {
    const dto = makeDto(DistrictDto, { district: 'Bình Thạnh' });
    const errors = await validate(dto);
    expect(errors).toHaveLength(0);
  });

  it('accepts "Thành phố Thủ Đức"', async () => {
    const dto = makeDto(DistrictDto, { district: 'Thành phố Thủ Đức' });
    const errors = await validate(dto);
    expect(errors).toHaveLength(0);
  });

  it('rejects single character', async () => {
    const dto = makeDto(DistrictDto, { district: 'Q' });
    const errors = await validate(dto);
    expect(errors).toHaveLength(1);
    expect(errors[0].constraints).toHaveProperty('isVietnamDistrict');
  });

  it('rejects empty string', async () => {
    const dto = makeDto(DistrictDto, { district: '' });
    const errors = await validate(dto);
    expect(errors).toHaveLength(1);
  });

  it('rejects non-string value', async () => {
    const dto = makeDto(DistrictDto, { district: 123 as unknown as string });
    const errors = await validate(dto);
    expect(errors).toHaveLength(1);
  });
});

describe('IsVND', () => {
  it('accepts valid VND amount (number)', async () => {
    const dto = makeDto(AmountDto, { amount: 500_000 });
    const errors = await validate(dto);
    expect(errors).toHaveLength(0);
  });

  it('accepts minimum amount (1)', async () => {
    const dto = makeDto(AmountDto, { amount: 1 });
    const errors = await validate(dto);
    expect(errors).toHaveLength(0);
  });

  it('accepts maximum amount (100 tỷ)', async () => {
    const dto = makeDto(AmountDto, { amount: 100_000_000_000 });
    const errors = await validate(dto);
    expect(errors).toHaveLength(0);
  });

  it('rejects zero', async () => {
    const dto = makeDto(AmountDto, { amount: 0 });
    const errors = await validate(dto);
    expect(errors).toHaveLength(1);
    expect(errors[0].constraints).toHaveProperty('isVND');
  });

  it('rejects negative amount', async () => {
    const dto = makeDto(AmountDto, { amount: -1000 });
    const errors = await validate(dto);
    expect(errors).toHaveLength(1);
  });

  it('rejects amount exceeding max', async () => {
    const dto = makeDto(AmountDto, { amount: 100_000_000_001 });
    const errors = await validate(dto);
    expect(errors).toHaveLength(1);
  });

  it('rejects non-integer', async () => {
    const dto = makeDto(AmountDto, { amount: 1000.5 });
    const errors = await validate(dto);
    expect(errors).toHaveLength(1);
  });

  it('rejects string value', async () => {
    const dto = makeDto(AmountDto, { amount: '500000' as unknown as number });
    const errors = await validate(dto);
    expect(errors).toHaveLength(1);
  });

  it('respects custom min/max options', async () => {
    const dtoOk = makeDto(CustomAmountDto, { amount: 100_000 });
    expect(await validate(dtoOk)).toHaveLength(0);

    const dtoBelowMin = makeDto(CustomAmountDto, { amount: 5_000 });
    expect(await validate(dtoBelowMin)).toHaveLength(1);

    const dtoAboveMax = makeDto(CustomAmountDto, { amount: 60_000_000 });
    expect(await validate(dtoAboveMax)).toHaveLength(1);
  });

  it('accepts bigint values', async () => {
    const dto = makeDto(AmountDto, { amount: 500_000n as unknown as number });
    const errors = await validate(dto);
    expect(errors).toHaveLength(0);
  });
});
