import { resetMemoryStorage } from '../storage/memory';
import { getStores, addStore } from '../stores';
import { createGroup, deleteGroup, addGroupMember, removeGroupMember, updateGroupMember, setGroupState } from '../groups';
import { GroupState } from '../../../types/gen/types.gen';

const CREATOR = 'tomas';
const USER = 'petr';
const ADMIN_PW = 'testadmin';
const STORE = 'McDonald\'s';
const TODAY = new Date('2025-01-10');

beforeEach(async () => {
    resetMemoryStorage();
    process.env.ADMIN_PASSWORD = ADMIN_PW;
    await addStore(STORE, ADMIN_PW);
});

afterEach(() => {
    delete process.env.ADMIN_PASSWORD;
});

describe('createGroup', () => {
    test('vytvoří skupinu, creator je člen', async () => {
        const data = await createGroup(CREATOR, STORE, TODAY);
        expect(data.groups).toHaveLength(1);
        const group = data.groups![0];
        expect(group.name).toBe(STORE);
        expect(group.creatorLogin).toBe(CREATOR);
        expect(group.state).toBe(GroupState.OPEN);
        expect(group.members[CREATOR]).toBeDefined();
    });

    test('odmítne název mimo seznam obchodů', async () => {
        await expect(createGroup(CREATOR, 'Neznámý obchod', TODAY)).rejects.toThrow('seznam');
    });

    test('vygeneruje unikátní ID', async () => {
        const d1 = await createGroup(CREATOR, STORE, TODAY);
        const d2 = await createGroup(USER, STORE, TODAY);
        expect(d2.groups).toHaveLength(2);
        expect(d2.groups![1].id).not.toBe(d2.groups![0].id);
    });
});

describe('deleteGroup', () => {
    test('creator může smazat skupinu', async () => {
        const d = await createGroup(CREATOR, STORE, TODAY);
        const groupId = d.groups![0].id;
        const result = await deleteGroup(CREATOR, groupId, TODAY);
        expect(result.groups).toHaveLength(0);
    });

    test('nečlen nemůže smazat skupinu', async () => {
        const d = await createGroup(CREATOR, STORE, TODAY);
        const groupId = d.groups![0].id;
        await expect(deleteGroup(USER, groupId, TODAY)).rejects.toThrow('zakladatel');
    });

    test('smazání neexistující skupiny vyhodí chybu', async () => {
        await expect(deleteGroup(CREATOR, 'nonexistent', TODAY)).rejects.toThrow('nalezena');
    });
});

describe('addGroupMember', () => {
    let groupId: string;

    beforeEach(async () => {
        const d = await createGroup(CREATOR, STORE, TODAY);
        groupId = d.groups![0].id;
    });

    test('uživatel se může přidat sám (open)', async () => {
        const d = await addGroupMember(USER, groupId, USER, TODAY);
        expect(d.groups![0].members[USER]).toBeDefined();
    });

    test('creator může přidat jiného uživatele', async () => {
        const d = await addGroupMember(CREATOR, groupId, USER, TODAY);
        expect(d.groups![0].members[USER]).toBeDefined();
    });

    test('nečlen nemůže přidat jiného uživatele', async () => {
        await expect(addGroupMember(USER, groupId, 'dalsi', TODAY)).rejects.toThrow('zakladatel');
    });

    test('nelze přidat do skupiny ve stavu ordered', async () => {
        await setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY);
        await setGroupState(CREATOR, groupId, GroupState.ORDERED, TODAY);
        await expect(addGroupMember(USER, groupId, USER, TODAY)).rejects.toThrow('objednáno');
    });

    test('nelze přidat existujícího člena', async () => {
        await expect(addGroupMember(CREATOR, groupId, CREATOR, TODAY)).rejects.toThrow('již');
    });
});

describe('removeGroupMember', () => {
    let groupId: string;

    beforeEach(async () => {
        const d = await createGroup(CREATOR, STORE, TODAY);
        groupId = d.groups![0].id;
        await addGroupMember(CREATOR, groupId, USER, TODAY);
    });

    test('člen se může odhlásit sám', async () => {
        const d = await removeGroupMember(USER, groupId, USER, TODAY);
        expect(d.groups![0].members[USER]).toBeUndefined();
    });

    test('creator může odebrat jiného člena', async () => {
        const d = await removeGroupMember(CREATOR, groupId, USER, TODAY);
        expect(d.groups![0].members[USER]).toBeUndefined();
    });

    test('nelze odebrat zakladatele', async () => {
        await expect(removeGroupMember(CREATOR, groupId, CREATOR, TODAY)).rejects.toThrow('Zakladatel');
    });

    test('nečlen nemůže odebrat jiného', async () => {
        await expect(removeGroupMember(USER, groupId, CREATOR, TODAY)).rejects.toThrow('zakladatel');
    });
});

describe('updateGroupMember', () => {
    let groupId: string;

    beforeEach(async () => {
        const d = await createGroup(CREATOR, STORE, TODAY);
        groupId = d.groups![0].id;
        await addGroupMember(CREATOR, groupId, USER, TODAY);
    });

    test('člen může upravit svá data (open)', async () => {
        const d = await updateGroupMember(USER, groupId, USER, { amount: 150, note: 'Big Mac' }, TODAY);
        expect(d.groups![0].members[USER].amount).toBe(150);
        expect(d.groups![0].members[USER].note).toBe('Big Mac');
    });

    test('creator může upravit data jiného člena', async () => {
        const d = await updateGroupMember(CREATOR, groupId, USER, { amount: 200 }, TODAY);
        expect(d.groups![0].members[USER].amount).toBe(200);
    });

    test('člen nemůže upravit data jiného (locked)', async () => {
        await setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY);
        await expect(updateGroupMember(USER, groupId, USER, { amount: 100 }, TODAY)).rejects.toThrow('uzamčeno');
    });

    test('nikdo nemůže upravit při stavu ordered', async () => {
        await setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY);
        await setGroupState(CREATOR, groupId, GroupState.ORDERED, TODAY);
        await expect(updateGroupMember(CREATOR, groupId, USER, { amount: 100 }, TODAY)).rejects.toThrow('objednáno');
    });
});

describe('setGroupState', () => {
    let groupId: string;

    beforeEach(async () => {
        const d = await createGroup(CREATOR, STORE, TODAY);
        groupId = d.groups![0].id;
    });

    test('open → locked', async () => {
        const d = await setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY);
        expect(d.groups![0].state).toBe(GroupState.LOCKED);
    });

    test('locked → open (odemčení)', async () => {
        await setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY);
        const d = await setGroupState(CREATOR, groupId, GroupState.OPEN, TODAY);
        expect(d.groups![0].state).toBe(GroupState.OPEN);
    });

    test('locked → ordered', async () => {
        await setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY);
        const d = await setGroupState(CREATOR, groupId, GroupState.ORDERED, TODAY);
        expect(d.groups![0].state).toBe(GroupState.ORDERED);
    });

    test('open → ordered není povoleno', async () => {
        await expect(setGroupState(CREATOR, groupId, GroupState.ORDERED, TODAY)).rejects.toThrow('Nelze přejít');
    });

    test('ordered je terminální stav', async () => {
        await setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY);
        await setGroupState(CREATOR, groupId, GroupState.ORDERED, TODAY);
        await expect(setGroupState(CREATOR, groupId, GroupState.OPEN, TODAY)).rejects.toThrow('Nelze přejít');
    });

    test('nečlen nemůže měnit stav', async () => {
        await expect(setGroupState(USER, groupId, GroupState.LOCKED, TODAY)).rejects.toThrow('zakladatel');
    });
});
