import { VnAdminService } from '../vn-admin.service';

describe('VnAdminService', () => {
  let service: VnAdminService;
  let mockPrisma: {
    vnProvince: { findMany: ReturnType<typeof vi.fn> };
    vnDistrict: { findMany: ReturnType<typeof vi.fn>; findFirst: ReturnType<typeof vi.fn> };
    vnWard: { findMany: ReturnType<typeof vi.fn> };
    vnAdministrativeAlias: { findFirst: ReturnType<typeof vi.fn> };
  };

  beforeEach(() => {
    mockPrisma = {
      vnProvince: { findMany: vi.fn() },
      vnDistrict: { findMany: vi.fn(), findFirst: vi.fn() },
      vnWard: { findMany: vi.fn() },
      vnAdministrativeAlias: { findFirst: vi.fn() },
    };
    service = new VnAdminService(mockPrisma as any);
  });

  // ── listProvinces ──────────────────────────────────────────────────

  describe('listProvinces', () => {
    it('returns all provinces ordered by name ascending', async () => {
      const provinces = [
        { code: '01', name: 'Hà Nội', type: 'thanh_pho', codename: 'thanh_pho_ha_noi' },
        { code: '79', name: 'TP Hồ Chí Minh', type: 'thanh_pho', codename: 'thanh_pho_ho_chi_minh' },
      ];
      mockPrisma.vnProvince.findMany.mockResolvedValue(provinces);

      const result = await service.listProvinces();

      expect(result).toEqual(provinces);
      expect(mockPrisma.vnProvince.findMany).toHaveBeenCalledWith({
        orderBy: [{ name: 'asc' }],
        select: { code: true, name: true, type: true, codename: true },
      });
    });

    it('returns an empty array when no provinces exist', async () => {
      mockPrisma.vnProvince.findMany.mockResolvedValue([]);

      const result = await service.listProvinces();

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

  // ── listDistricts ──────────────────────────────────────────────────

  describe('listDistricts', () => {
    it('returns districts filtered by provinceCode', async () => {
      const districts = [
        { code: '760', provinceCode: '79', name: 'Quận 1', type: 'quan', codename: 'quan_1' },
        { code: '769', provinceCode: '79', name: 'Thành phố Thủ Đức', type: 'thanh_pho', codename: 'thanh_pho_thu_duc' },
      ];
      mockPrisma.vnDistrict.findMany.mockResolvedValue(districts);

      const result = await service.listDistricts('79');

      expect(result).toEqual(districts);
      expect(mockPrisma.vnDistrict.findMany).toHaveBeenCalledWith({
        where: { provinceCode: '79' },
        orderBy: [{ name: 'asc' }],
        select: { code: true, provinceCode: true, name: true, type: true, codename: true },
      });
    });

    it('returns an empty array when province has no districts', async () => {
      mockPrisma.vnDistrict.findMany.mockResolvedValue([]);

      const result = await service.listDistricts('99');

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

  // ── listWards ──────────────────────────────────────────────────────

  describe('listWards', () => {
    it('returns wards filtered by districtCode', async () => {
      const wards = [
        { code: '26734', districtCode: '760', name: 'Phường Bến Nghé', type: 'phuong', codename: 'phuong_ben_nghe' },
        { code: '26737', districtCode: '760', name: 'Phường Bến Thành', type: 'phuong', codename: 'phuong_ben_thanh' },
      ];
      mockPrisma.vnWard.findMany.mockResolvedValue(wards);

      const result = await service.listWards('760');

      expect(result).toEqual(wards);
      expect(mockPrisma.vnWard.findMany).toHaveBeenCalledWith({
        where: { districtCode: '760' },
        orderBy: [{ name: 'asc' }],
        select: { code: true, districtCode: true, name: true, type: true, codename: true },
      });
    });

    it('returns an empty array when district has no wards', async () => {
      mockPrisma.vnWard.findMany.mockResolvedValue([]);

      const result = await service.listWards('999');

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

  // ── resolveDistrict ────────────────────────────────────────────────

  describe('resolveDistrict', () => {
    it('returns null resolution for empty string input', async () => {
      const result = await service.resolveDistrict('');

      expect(result).toEqual({
        districtCode: null,
        isCurrent: false,
        resolvedViaAlias: false,
        aliasReason: null,
      });
      expect(mockPrisma.vnDistrict.findFirst).not.toHaveBeenCalled();
      expect(mockPrisma.vnAdministrativeAlias.findFirst).not.toHaveBeenCalled();
    });

    it('returns null resolution for whitespace-only input', async () => {
      const result = await service.resolveDistrict('   ');

      expect(result).toEqual({
        districtCode: null,
        isCurrent: false,
        resolvedViaAlias: false,
        aliasReason: null,
      });
    });

    it('resolves a current district name directly', async () => {
      mockPrisma.vnDistrict.findFirst.mockResolvedValue({ code: '760' });

      const result = await service.resolveDistrict('Quận 1');

      expect(result).toEqual({
        districtCode: '760',
        isCurrent: true,
        resolvedViaAlias: false,
        aliasReason: null,
      });
      expect(mockPrisma.vnDistrict.findFirst).toHaveBeenCalledWith({
        where: { name: 'Quận 1' },
        select: { code: true },
      });
    });

    it('filters by provinceCode when provided', async () => {
      mockPrisma.vnDistrict.findFirst.mockResolvedValue({ code: '760' });

      await service.resolveDistrict('Quận 1', '79');

      expect(mockPrisma.vnDistrict.findFirst).toHaveBeenCalledWith({
        where: { name: 'Quận 1', provinceCode: '79' },
        select: { code: true },
      });
    });

    it('resolves a historical alias when no direct match is found', async () => {
      mockPrisma.vnDistrict.findFirst.mockResolvedValue(null);
      mockPrisma.vnAdministrativeAlias.findFirst.mockResolvedValue({
        newDistrictCode: '769',
        reason: 'Sáp nhập vào Thủ Đức',
      });

      const result = await service.resolveDistrict('Quận 2');

      expect(result).toEqual({
        districtCode: '769',
        isCurrent: false,
        resolvedViaAlias: true,
        aliasReason: 'Sáp nhập vào Thủ Đức',
      });
      expect(mockPrisma.vnAdministrativeAlias.findFirst).toHaveBeenCalledWith({
        where: { oldName: 'Quận 2', level: 'district' },
        select: { newDistrictCode: true, reason: true },
      });
    });

    it('returns null resolution when neither direct nor alias match', async () => {
      mockPrisma.vnDistrict.findFirst.mockResolvedValue(null);
      mockPrisma.vnAdministrativeAlias.findFirst.mockResolvedValue(null);

      const result = await service.resolveDistrict('Nonexistent District');

      expect(result).toEqual({
        districtCode: null,
        isCurrent: false,
        resolvedViaAlias: false,
        aliasReason: null,
      });
    });

    it('returns null resolution when alias exists but has no newDistrictCode', async () => {
      mockPrisma.vnDistrict.findFirst.mockResolvedValue(null);
      mockPrisma.vnAdministrativeAlias.findFirst.mockResolvedValue({
        newDistrictCode: null,
        reason: 'Removed',
      });

      const result = await service.resolveDistrict('Old Name');

      expect(result).toEqual({
        districtCode: null,
        isCurrent: false,
        resolvedViaAlias: false,
        aliasReason: null,
      });
    });

    it('trims whitespace from input before matching', async () => {
      mockPrisma.vnDistrict.findFirst.mockResolvedValue({ code: '760' });

      await service.resolveDistrict('  Quận 1  ');

      expect(mockPrisma.vnDistrict.findFirst).toHaveBeenCalledWith({
        where: { name: 'Quận 1' },
        select: { code: true },
      });
    });

    it('skips alias lookup when direct match succeeds', async () => {
      mockPrisma.vnDistrict.findFirst.mockResolvedValue({ code: '760' });

      await service.resolveDistrict('Quận 1');

      expect(mockPrisma.vnAdministrativeAlias.findFirst).not.toHaveBeenCalled();
    });
  });

  // ── isKnownDistrictName ────────────────────────────────────────────

  describe('isKnownDistrictName', () => {
    it('returns true when resolveDistrict finds a districtCode', async () => {
      mockPrisma.vnDistrict.findFirst.mockResolvedValue({ code: '760' });

      const result = await service.isKnownDistrictName('Quận 1');

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

    it('returns false when resolveDistrict finds no districtCode', async () => {
      mockPrisma.vnDistrict.findFirst.mockResolvedValue(null);
      mockPrisma.vnAdministrativeAlias.findFirst.mockResolvedValue(null);

      const result = await service.isKnownDistrictName('Unknown District');

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

    it('returns true when district is resolved via alias', async () => {
      mockPrisma.vnDistrict.findFirst.mockResolvedValue(null);
      mockPrisma.vnAdministrativeAlias.findFirst.mockResolvedValue({
        newDistrictCode: '769',
        reason: 'Merged',
      });

      const result = await service.isKnownDistrictName('Quận 9');

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