import { test, it, expect, mock, beforeEach } from 'bun:test';
import { getUnit, renderUnit, isUnitInversePercentage, type Unit } from './unit';

test('unit.ts', () => {
  test('getUnit', () => {
    it('should return unit when found', async () => {
      const mockUnit: Unit = {
        unit_id: 123,
        display_name: 'Test Unit',
        description: { en: 'Test description' },
        name: { en: 'Test Name' },
      };

      mock.module('@star-kitten/util/json-query.js', () => ({
        queryJsonObject: () => Promise.resolve(mockUnit),
      }));

      const result = await getUnit(123);

      expect(result).toEqual(mockUnit);
    });

    it('should throw error when unit not found', async () => {
      mock.module('@star-kitten/util/json-query.js', () => ({
        queryJsonObject: () => Promise.resolve(null),
      }));

      await expect(getUnit(999)).rejects.toThrow('Unit ID 999 not found in reference data');
    });
  });

  test('renderUnit', () => {
    const mockUnit: Unit = {
      unit_id: 0,
      display_name: 'Test Unit',
      description: { en: 'Test description' },
      name: { en: 'Test Name' },
    };

    test('inverse percentage units', () => {
      it('should render unit 108 as inverse percentage', async () => {
        const unit = { ...mockUnit, unit_id: 108 };
        const result = await renderUnit(unit, 0.75);
        expect(result).toBe('0.25 Test Unit');
      });

      it('should render unit 111 as inverse percentage', async () => {
        const unit = { ...mockUnit, unit_id: 111 };
        const result = await renderUnit(unit, 0.3);
        expect(result).toBe('0.70 Test Unit');
      });

      it('should handle missing display_name for inverse percentage', async () => {
        const unit = { ...mockUnit, unit_id: 108, display_name: '' };
        const result = await renderUnit(unit, 0.5);
        expect(result).toBe('0.50 ');
      });
    });

    test('time units', () => {
      it('should render unit 3 (seconds) using convertSecondsToTimeString', async () => {
        const unit = { ...mockUnit, unit_id: 3 };
        mock.module('@star-kitten/util/text.js', () => ({
          convertSecondsToTimeString: (seconds: number) => {
            if (seconds === 330) return '5m 30s';
            return '0s';
          },
        }));

        const result = await renderUnit(unit, 330);

        expect(result).toBe('5m 30s');
      });

      it('should render unit 101 (milliseconds) using convertMillisecondsToTimeString', async () => {
        const unit = { ...mockUnit, unit_id: 101 };
        mock.module('@star-kitten/util/text.js', () => ({
          convertMillisecondsToTimeString: (milliseconds: number) => {
            if (milliseconds === 2500) return '2.5s';
            return '0s';
          },
        }));

        const result = await renderUnit(unit, 2500);

        expect(result).toBe('2.5s');
      });
    });

    test('size class unit (117)', () => {
      it('should render size class 1 as Small', async () => {
        const unit = { ...mockUnit, unit_id: 117 };
        const result = await renderUnit(unit, 1);
        expect(result).toBe('Small');
      });

      it('should render size class 2 as Medium', async () => {
        const unit = { ...mockUnit, unit_id: 117 };
        const result = await renderUnit(unit, 2);
        expect(result).toBe('Medium');
      });

      it('should render size class 3 as Large', async () => {
        const unit = { ...mockUnit, unit_id: 117 };
        const result = await renderUnit(unit, 3);
        expect(result).toBe('Large');
      });

      it('should render size class 4 as X-Large', async () => {
        const unit = { ...mockUnit, unit_id: 117 };
        const result = await renderUnit(unit, 4);
        expect(result).toBe('X-Large');
      });

      it('should render unknown size class as Unknown', async () => {
        const unit = { ...mockUnit, unit_id: 117 };
        const result = await renderUnit(unit, 99);
        expect(result).toBe('Unknown');
      });
    });

    test('specialized units', () => {
      it('should render unit 141 (hardpoints) as string', async () => {
        const unit = { ...mockUnit, unit_id: 141 };
        const result = await renderUnit(unit, 8);
        expect(result).toBe('8');
      });

      it('should render unit 120 (calibration) with pts suffix', async () => {
        const unit = { ...mockUnit, unit_id: 120 };
        const result = await renderUnit(unit, 400);
        expect(result).toBe('400 pts');
      });

      it('should render unit 116 (typeID) using type lookup', async () => {
        const unit = { ...mockUnit, unit_id: 116 };
        const mockType = {
          type_id: 12345,
          name: { en: 'Test Type' },
          description: { en: 'Test description' },
          published: true,
        };

        mock.module('./type', () => ({
          getType: (type_id: number) => {
            if (type_id === 12345) return Promise.resolve(mockType);
            return Promise.resolve(null);
          },
          renderTypeEveRefLink: (type: any, lang: string) => {
            if (type.type_id === 12345 && lang === 'en') return 'Type Link';
            return null;
          },
        }));

        const result = await renderUnit(unit, 12345, 'en');

        expect(result).toBe('Type Link');
      });

      it('should render unit 116 (typeID) as Unknown when link is null', async () => {
        const unit = { ...mockUnit, unit_id: 116 };
        const mockType = {
          type_id: 12345,
          name: { en: 'Test Type' },
          description: { en: 'Test description' },
          published: true,
        };

        mock.module('./type', () => ({
          getType: (type_id: number) => {
            return Promise.resolve(null);
          },
          renderTypeEveRefLink: (type: any, lang: string) => {
            return null;
          },
        }));

        const result = await renderUnit(unit, 12345);

        expect(result).toBe('Unknown');
      });

      it('should render unit 115 (groupID) using group lookup', async () => {
        const unit = { ...mockUnit, unit_id: 115 };
        const mockGroup = {
          group_id: 67890,
          name: { en: 'Test Group' },
          category_id: 1,
          published: true,
          anchorable: false,
          anchored: false,
          fittable_non_singleton: false,
          use_base_price: false,
        };

        mock.module('./group', () => ({
          getGroup: () => Promise.resolve(mockGroup),
          renderGroupEveRefLink: (group: any, lang: string) => {
            if (group.group_id === 67890 && lang === 'fr') return 'Group Link';
            return null;
          },
        }));

        const result = await renderUnit(unit, 67890, 'fr');

        expect(result).toBe('Group Link');
      });

      it('should render unit 115 (groupID) as Unknown when link is null', async () => {
        const unit = { ...mockUnit, unit_id: 115 };
        const mockGroup = {
          group_id: 67890,
          name: { en: 'Test Group' },
          category_id: 1,
          published: true,
          anchorable: false,
          anchored: false,
          fittable_non_singleton: false,
          use_base_price: false,
        };

        mock.module('./group', () => ({
          getGroup: (group_id: number) => {
            return Promise.resolve(null);
          },
          renderGroupEveRefLink: (group: any, lang: string) => {
            return null;
          },
        }));

        const result = await renderUnit(unit, 67890);

        expect(result).toBe('Unknown');
      });
    });

    test('physical units', () => {
      it('should render unit 10 (m/s)', async () => {
        const unit = { ...mockUnit, unit_id: 10 };
        const result = await renderUnit(unit, 150);
        expect(result).toBe('150 m/s');
      });

      it('should render unit 11 (m/s²)', async () => {
        const unit = { ...mockUnit, unit_id: 11 };
        const result = await renderUnit(unit, 9.8);
        expect(result).toBe('9.8 m/s²');
      });

      it('should render unit 9 (m³)', async () => {
        const unit = { ...mockUnit, unit_id: 9 };
        const result = await renderUnit(unit, 1000);
        expect(result).toBe('1000 m³');
      });

      it('should render unit 8 (m²)', async () => {
        const unit = { ...mockUnit, unit_id: 8 };
        const result = await renderUnit(unit, 50);
        expect(result).toBe('50 m²');
      });

      it('should render unit 12 (m⁻¹)', async () => {
        const unit = { ...mockUnit, unit_id: 12 };
        const result = await renderUnit(unit, 0.1);
        expect(result).toBe('0.1 m⁻¹');
      });

      it('should render unit 128 (Mbps)', async () => {
        const unit = { ...mockUnit, unit_id: 128 };
        const result = await renderUnit(unit, 100);
        expect(result).toBe('100 Mbps');
      });
    });

    test('default case', () => {
      it('should render unknown unit with value and display_name', async () => {
        const unit = { ...mockUnit, unit_id: 999, display_name: 'Custom Unit' };
        const result = await renderUnit(unit, 42);
        expect(result).toBe('42 Custom Unit');
      });

      it('should render unknown unit with empty display_name', async () => {
        const unit = { ...mockUnit, unit_id: 999, display_name: '' };
        const result = await renderUnit(unit, 42);
        expect(result).toBe('42 ');
      });

      it('should handle undefined display_name', async () => {
        const unit = { ...mockUnit, unit_id: 999, display_name: undefined as any };
        const result = await renderUnit(unit, 42);
        expect(result).toBe('42 ');
      });
    });

    test('edge cases', () => {
      it('should handle zero values', async () => {
        const unit = { ...mockUnit, unit_id: 10 };
        const result = await renderUnit(unit, 0);
        expect(result).toBe('0 m/s');
      });

      it('should handle negative values', async () => {
        const unit = { ...mockUnit, unit_id: 120 };
        const result = await renderUnit(unit, -50);
        expect(result).toBe('-50 pts');
      });

      it('should handle very large values', async () => {
        const unit = { ...mockUnit, unit_id: 9 };
        const result = await renderUnit(unit, 1e10);
        expect(result).toBe('10000000000 m³');
      });

      it('should handle decimal values for inverse percentage', async () => {
        const unit = { ...mockUnit, unit_id: 108 };
        const result = await renderUnit(unit, 0.12345);
        expect(result).toBe('0.88 Test Unit');
      });

      it('should default to "en" locale when not specified', async () => {
        const unit = { ...mockUnit, unit_id: 116 };
        const mockType = {
          type_id: 12345,
          name: { en: 'Test Type' },
          description: { en: 'Test description' },
          published: true,
        };

        mock.module('./type', () => ({
          getType: (type_id: number) => {
            if (type_id === 12345) return Promise.resolve(mockType);
            return Promise.resolve(null);
          },
          renderTypeEveRefLink: (type: any, lang: string) => {
            if (type.type_id === 12345 && lang === 'en') return 'Type Link';
            return null;
          },
        }));

        const result = await renderUnit(unit, 12345);
        expect(result).toBe('Type Link');
      });
    });

    test('error handling', () => {
      it('should handle getType errors gracefully', async () => {
        const unit = { ...mockUnit, unit_id: 116 };
        mock.module('./type', () => ({
          getType: (type_id: number) => {
            throw new Error('Type not found');
          },
        }));

        await expect(renderUnit(unit, 12345)).rejects.toThrow('Type not found');
      });

      it('should handle getGroup errors gracefully', async () => {
        const unit = { ...mockUnit, unit_id: 115 };
        mock.module('./group', () => ({
          getGroup: (group_id: number) => {
            throw new Error('Group not found');
          },
        }));

        await expect(renderUnit(unit, 67890)).rejects.toThrow('Group not found');
      });
    });
  });

  test('isUnitInversePercentage', () => {
    const mockUnit: Unit = {
      unit_id: 0,
      display_name: 'Test Unit',
      description: { en: 'Test description' },
      name: { en: 'Test Name' },
    };

    it('should return true for unit_id 108', () => {
      const unit = { ...mockUnit, unit_id: 108 };
      expect(isUnitInversePercentage(unit)).toBe(true);
    });

    it('should return true for unit_id 111', () => {
      const unit = { ...mockUnit, unit_id: 111 };
      expect(isUnitInversePercentage(unit)).toBe(true);
    });

    it('should return false for other unit_ids', () => {
      const testCases = [0, 1, 3, 8, 9, 10, 11, 12, 101, 107, 109, 115, 116, 117, 120, 128, 141, 999];

      testCases.forEach((unit_id) => {
        const unit = { ...mockUnit, unit_id };
        expect(isUnitInversePercentage(unit)).toBe(false);
      });
    });

    it('should use loose equality (==) not strict equality (===)', () => {
      // Test that the function uses == comparison by verifying it works with string unit_ids
      const unit108 = { ...mockUnit, unit_id: 108 as any };
      const unit111 = { ...mockUnit, unit_id: 111 as any };

      expect(isUnitInversePercentage(unit108)).toBe(true);
      expect(isUnitInversePercentage(unit111)).toBe(true);
    });
  });
});
