import { describe, expect, it } from 'vitest';
import { buildInitialComposition, normalizeComposition, resolveBedroomCount, type RawCompositionRoom } from './composition-init';
import { ROOM_TYPES } from './constants';

const { BEDROOM, BATHROOM, KITCHEN, LIVING_ROOM, WC } = ROOM_TYPES;

const bedroom = (amenities: number[] = []) => ({ id: BEDROOM, amenities });
const bathroom = (amenities: number[] = []) => ({ id: BATHROOM, amenities });

describe('normalizeComposition', () => {
    it('coerces string ids and amenities to numbers (raw edit-endpoint payload)', () => {
        const raw: RawCompositionRoom[] = [
            { id: '257', amenities: ['1', '2'] },
            { id: '81', amenities: [3, '4'] },
        ];
        expect(normalizeComposition(raw)).toEqual([
            { id: 257, amenities: [1, 2] },
            { id: 81, amenities: [3, 4] },
        ]);
    });

    it('keeps numeric ids and amenities untouched', () => {
        expect(normalizeComposition([{ id: 101, amenities: [47, 96] }])).toEqual([{ id: 101, amenities: [47, 96] }]);
    });

    it('returns [] for null input', () => {
        expect(normalizeComposition(null)).toEqual([]);
    });

    it('returns [] for undefined input', () => {
        expect(normalizeComposition(undefined)).toEqual([]);
    });

    it('defaults missing amenities to an empty array', () => {
        expect(normalizeComposition([{ id: 257 }])).toEqual([{ id: 257, amenities: [] }]);
    });
});

describe('resolveBedroomCount', () => {
    it('counts bedroom rooms (id 257) when the composition is non-empty', () => {
        const composition = [bedroom(), bathroom(), bedroom([12]), { id: KITCHEN, amenities: [] }];
        expect(resolveBedroomCount(composition, 5, 9)).toBe(2);
    });

    it('returns 0 for a legitimate studio: rooms present but none is a bedroom (no type fallback)', () => {
        const composition = [{ id: LIVING_ROOM, amenities: [] }, bathroom(), { id: WC, amenities: [] }];
        expect(resolveBedroomCount(composition, 3, '4')).toBe(0);
    });

    it('falls back to typeNumberOfRooms when the composition is empty', () => {
        expect(resolveBedroomCount([], 3, 9)).toBe(3);
    });

    it('accepts 0 as a valid typeNumberOfRooms fallback', () => {
        expect(resolveBedroomCount([], 0, 9)).toBe(0);
    });

    it('falls back to Number(noOfUnits) when typeNumberOfRooms is null', () => {
        expect(resolveBedroomCount([], null, '4')).toBe(4);
        expect(resolveBedroomCount([], null, 2)).toBe(2);
    });

    it('falls back to Number(noOfUnits) when typeNumberOfRooms is undefined', () => {
        expect(resolveBedroomCount([], undefined, '7')).toBe(7);
    });

    it('returns 0 when noOfUnits is not numeric', () => {
        expect(resolveBedroomCount([], null, 'abc')).toBe(0);
    });

    it('returns 0 when noOfUnits is NaN', () => {
        expect(resolveBedroomCount([], null, NaN)).toBe(0);
    });

    it('returns 0 when noOfUnits is undefined', () => {
        expect(resolveBedroomCount([], null, undefined)).toBe(0);
    });
});

describe('buildInitialComposition', () => {
    it('NEVER truncates bedrooms: 5 saved bedrooms survive a stale target of 1 (key regression)', () => {
        const raw: RawCompositionRoom[] = [bedroom([1]), bedroom([2]), bedroom([3]), bedroom([4]), bedroom([5])];
        const result = buildInitialComposition(raw, 1, 0);
        expect(result).toHaveLength(5);
        expect(result.filter((room) => room.id === BEDROOM)).toHaveLength(5);
        expect(result.map((room) => room.amenities)).toEqual([[1], [2], [3], [4], [5]]);
    });

    it('never truncates bathrooms either when the target is below the existing count', () => {
        const raw: RawCompositionRoom[] = [bathroom([10]), bathroom([11]), bathroom([12])];
        const result = buildInitialComposition(raw, 0, 1);
        expect(result.filter((room) => room.id === BATHROOM)).toHaveLength(3);
    });

    it('pads bedrooms up to the target with empty cards: 1 existing, target 3 → +2 empty', () => {
        const raw: RawCompositionRoom[] = [bedroom([42])];
        const result = buildInitialComposition(raw, 3, 0);
        expect(result).toEqual([
            { id: BEDROOM, amenities: [42] },
            { id: BEDROOM, amenities: [] },
            { id: BEDROOM, amenities: [] },
        ]);
    });

    it('pads bathrooms up to the target with empty cards', () => {
        const result = buildInitialComposition([bathroom([7])], 0, 2);
        expect(result).toEqual([
            { id: BATHROOM, amenities: [7] },
            { id: BATHROOM, amenities: [] },
        ]);
    });

    it('orders the result as [others..., bedrooms..., bathrooms...]', () => {
        const raw: RawCompositionRoom[] = [bedroom([1]), { id: KITCHEN, amenities: [20] }, bathroom([30]), { id: LIVING_ROOM, amenities: [] }];
        const result = buildInitialComposition(raw, 2, 2);
        expect(result.map((room) => room.id)).toEqual([KITCHEN, LIVING_ROOM, BEDROOM, BEDROOM, BATHROOM, BATHROOM]);
    });

    it('preserves existing amenities through normalization and padding', () => {
        const raw: RawCompositionRoom[] = [
            { id: '257', amenities: ['5', 6] },
            { id: '81', amenities: ['9'] },
        ];
        const result = buildInitialComposition(raw, 2, 1);
        expect(result).toEqual([
            { id: BEDROOM, amenities: [5, 6] },
            { id: BEDROOM, amenities: [] },
            { id: BATHROOM, amenities: [9] },
        ]);
    });

    it('builds pure padding from a null raw composition', () => {
        expect(buildInitialComposition(null, 2, 1)).toEqual([
            { id: BEDROOM, amenities: [] },
            { id: BEDROOM, amenities: [] },
            { id: BATHROOM, amenities: [] },
        ]);
    });

    it('returns [] for an empty composition with zero targets', () => {
        expect(buildInitialComposition(undefined, 0, 0)).toEqual([]);
    });
});
