import {
    useRecentlyUsedConstraints,
    areConstraintsEqual,
} from './useRecentlyUsedConstraints';
import { renderHook, act } from '@testing-library/react';
import type { IConstraint } from 'interfaces/strategy';
import { IN, STR_CONTAINS } from 'constants/operators';
import type { Operator } from 'constants/operators';

const createTestConstraint = (
    contextName: string,
    operator: Operator = IN,
    values = [contextName],
): IConstraint => ({
    contextName,
    operator,
    values,
});

describe('areConstraintsEqual', () => {
    it('should return true for constraints with identical content', () => {
        const constraint1: IConstraint = {
            contextName: 'userId',
            operator: IN,
            values: ['user1', 'user2'],
            inverted: false,
        };

        const constraint2: IConstraint = {
            contextName: 'userId',
            operator: IN,
            values: ['user1', 'user2'],
            inverted: false,
        };

        expect(areConstraintsEqual(constraint1, constraint2)).toBe(true);
    });

    it('should return true for constraints with same values in different order', () => {
        const constraint1: IConstraint = {
            contextName: 'userId',
            operator: IN,
            values: ['user1', 'user2', 'user3'],
        };

        const constraint2: IConstraint = {
            contextName: 'userId',
            operator: IN,
            values: ['user2', 'user3', 'user1'],
        };

        expect(areConstraintsEqual(constraint1, constraint2)).toBe(true);
    });

    it('should return false for constraints with different content', () => {
        const constraint1: IConstraint = {
            contextName: 'userId',
            operator: IN,
            values: ['user1', 'user2'],
        };

        const constraint2: IConstraint = {
            contextName: 'userId',
            operator: IN,
            values: ['user1', 'user3'],
        };

        expect(areConstraintsEqual(constraint1, constraint2)).toBe(false);
    });

    it('should return false for constraints with different operators', () => {
        const constraint1: IConstraint = {
            contextName: 'userId',
            operator: IN,
            values: ['user1'],
        };

        const constraint2: IConstraint = {
            contextName: 'userId',
            operator: STR_CONTAINS,
            values: ['user1'],
        };

        expect(areConstraintsEqual(constraint1, constraint2)).toBe(false);
    });
});

describe('useRecentlyUsedConstraints', () => {
    beforeEach(() => {
        window.localStorage.clear();
    });

    it('should initialize with empty array when no items in localStorage', () => {
        const { result } = renderHook(() => useRecentlyUsedConstraints());

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

    it('should initialize with initial items if provided', () => {
        const initialItems = [createTestConstraint('userId')];
        const { result } = renderHook(() =>
            useRecentlyUsedConstraints(initialItems),
        );

        expect(result.current.items).toEqual(initialItems);
    });

    it('should add new items to the beginning of the list', () => {
        const { result } = renderHook(() => useRecentlyUsedConstraints());

        act(() => {
            result.current.addItem(createTestConstraint('userId'));
        });
        expect(result.current.items[0].contextName).toBe('userId');

        act(() => {
            result.current.addItem(createTestConstraint('email'));
        });
        expect(result.current.items[0].contextName).toBe('email');
        expect(result.current.items[1].contextName).toBe('userId');
    });

    it('should handle array of constraints when adding items', () => {
        const { result } = renderHook(() => useRecentlyUsedConstraints());

        const constraints = [
            createTestConstraint('userId'),
            createTestConstraint('email'),
            createTestConstraint('appName'),
        ];

        act(() => {
            result.current.addItem(constraints);
        });

        expect(result.current.items.length).toBe(3);
        expect(result.current.items[0].contextName).toBe('appName');
        expect(result.current.items[1].contextName).toBe('email');
        expect(result.current.items[2].contextName).toBe('userId');
    });

    it('should limit stored items to maximum of 3', () => {
        const { result } = renderHook(() => useRecentlyUsedConstraints());

        act(() => {
            result.current.addItem(createTestConstraint('userId'));
            result.current.addItem(createTestConstraint('email'));
            result.current.addItem(createTestConstraint('countryId'));
            result.current.addItem(createTestConstraint('appName'));
        });

        expect(result.current.items.length).toBe(3);
        expect(result.current.items[0].contextName).toBe('appName');
        expect(result.current.items[1].contextName).toBe('countryId');
        expect(result.current.items[2].contextName).toBe('email');
    });

    it('should also limit to max of 3 items when adding an array of constraints', () => {
        const { result } = renderHook(() => useRecentlyUsedConstraints());

        const constraints = [
            createTestConstraint('userId'),
            createTestConstraint('email'),
            createTestConstraint('appName'),
            createTestConstraint('countryId'),
        ];

        act(() => {
            result.current.addItem(constraints);
        });

        expect(result.current.items.length).toBe(3);
    });

    it('should not add duplicate constraints', () => {
        const { result } = renderHook(() => useRecentlyUsedConstraints());

        const constraint1 = createTestConstraint('userId', IN, [
            'user1',
            'user2',
        ]);
        const constraint2 = createTestConstraint('email');

        act(() => {
            result.current.addItem(constraint1);
            result.current.addItem(constraint2);
        });
        expect(result.current.items.length).toBe(2);

        const duplicateConstraint = createTestConstraint('userId', IN, [
            'user1',
            'user2',
        ]);
        act(() => {
            result.current.addItem(duplicateConstraint);
        });

        expect(result.current.items.length).toBe(2);
        expect(result.current.items[0].contextName).toBe('userId');
        expect(result.current.items[1].contextName).toBe('email');
    });

    it('should not add duplicate constraints with values in different order', () => {
        const { result } = renderHook(() => useRecentlyUsedConstraints());

        const constraint1 = {
            contextName: 'userId',
            operator: IN,
            values: ['user1', 'user2', 'user3'],
        } as IConstraint;

        act(() => {
            result.current.addItem(constraint1);
        });
        expect(result.current.items.length).toBe(1);

        // Same constraint but with values in different order
        const sameConstraintDifferentOrder = {
            contextName: 'userId',
            operator: IN,
            values: ['user3', 'user1', 'user2'],
        } as IConstraint;

        act(() => {
            result.current.addItem(sameConstraintDifferentOrder);
        });

        // Should not add a duplicate, just move it to the top
        expect(result.current.items.length).toBe(1);
        expect(result.current.items[0].contextName).toBe('userId');
        // The values array in the stored item should be the one from the most recently added item
        expect(result.current.items[0].values).toEqual([
            'user3',
            'user1',
            'user2',
        ]);
    });

    it('should handle duplicates in an array of constraints', () => {
        const { result } = renderHook(() => useRecentlyUsedConstraints());

        const constraints = [
            createTestConstraint('userId', IN, ['user1', 'user2']),
            createTestConstraint('email'),
            createTestConstraint('userId', IN, ['user1', 'user2']), // Duplicate
        ];

        act(() => {
            result.current.addItem(constraints);
        });

        expect(result.current.items.length).toBe(2);
        expect(result.current.items[0].contextName).toBe('userId');
        expect(result.current.items[1].contextName).toBe('email');
    });

    it('should persist items to localStorage', () => {
        const { result } = renderHook(() => useRecentlyUsedConstraints());

        act(() => {
            result.current.addItem(createTestConstraint('userId'));
        });

        const { result: newResult } = renderHook(() =>
            useRecentlyUsedConstraints(),
        );

        expect(newResult.current.items[0].contextName).toBe('userId');
    });
});
