import { resetMemoryStorage } from '../storage/memory';
import getStorage from '../storage';
import { formatDate } from '../utils';
import { getToday } from '../service';
import {
    createPizzaDay,
    deletePizzaDay,
    addPizzaOrder,
    removePizzaOrder,
    removeAllUserPizzas,
    updatePizzaFee,
    lockPizzaDay,
    unlockPizzaDay,
    finishPizzaOrder,
    finishPizzaDelivery,
} from '../pizza';
import { ClientData, PizzaDayState } from '../../../types/gen/types.gen';

jest.mock('../notifikace', () => ({
    callNotifikace: jest.fn().mockResolvedValue([]),
}));
jest.mock('../qr', () => ({
    generateQr: jest.fn().mockResolvedValue(undefined),
}));
jest.mock('../chefie', () => ({
    downloadPizzy: jest.fn().mockResolvedValue([
        { id: 1, name: 'Margherita', variants: [{ varId: 10, size: 'střední', price: 150 }] },
    ]),
    downloadSalaty: jest.fn().mockResolvedValue([]),
}));

const CREATOR = 'kreator';
const USER = 'uzivatel';

const PIZZA: any = { id: 1, name: 'Margherita', ingredients: [], variants: [], sizes: [] };
const SIZE_M = { varId: 1, size: '30cm', price: 179, boxPrice: 13, pizzaPrice: 166 };
const SIZE_L = { varId: 2, size: '35cm', price: 219, boxPrice: 15, pizzaPrice: 204 };
const SIZE: any = { varId: 10, size: 'střední', price: 150 };

async function seedPizzaDay(state: PizzaDayState = PizzaDayState.CREATED): Promise<void> {
    const today = formatDate(getToday());
    const storage = getStorage();
    const data: ClientData = {
        todayDayIndex: 0,
        date: today,
        isWeekend: false,
        dayIndex: 0,
        choices: {},
        pizzaDay: {
            state,
            creator: CREATOR,
            orders: [],
        },
        pizzaList: [],
        salatList: [],
    };
    await storage.setData(today, data);
}

beforeEach(() => {
    resetMemoryStorage();
});

describe('createPizzaDay', () => {
    test('vytvoří pizza day ve stavu CREATED', async () => {
        const data = await createPizzaDay(CREATOR);
        expect(data.pizzaDay?.state).toBe(PizzaDayState.CREATED);
        expect(data.pizzaDay?.creator).toBe(CREATOR);
    });

    test('vyhodí chybu, pokud pizza day již existuje', async () => {
        await createPizzaDay(CREATOR);
        await expect(createPizzaDay(CREATOR)).rejects.toThrow('existuje');
    });
});

describe('deletePizzaDay', () => {
    test('smaže pizza day tvůrcem', async () => {
        await createPizzaDay(CREATOR);
        const data = await deletePizzaDay(CREATOR);
        expect(data.pizzaDay).toBeUndefined();
    });

    test('vyhodí chybu pro jiného uživatele', async () => {
        await createPizzaDay(CREATOR);
        await expect(deletePizzaDay(USER)).rejects.toThrow();
    });
});

describe('addPizzaOrder', () => {
    test('přidá objednávku pizzy', async () => {
        await seedPizzaDay();
        const data = await addPizzaOrder(USER, PIZZA, SIZE);
        const order = data.pizzaDay?.orders?.find(o => o.customer === USER);
        expect(order?.pizzaList?.length).toBe(1);
        expect(order?.totalPrice).toBe(SIZE.price);
    });

    test('přičte cenu další pizzy ke stejné objednávce', async () => {
        await seedPizzaDay();
        await addPizzaOrder(USER, PIZZA, SIZE_M);
        const data = await addPizzaOrder(USER, { ...PIZZA, name: 'Quattro' }, SIZE_L);
        const order = data.pizzaDay?.orders?.find(o => o.customer === USER);
        expect(order?.totalPrice).toBe(SIZE_M.price + SIZE_L.price);
        expect(order?.pizzaList).toHaveLength(2);
    });

    test('vyhodí chybu bez aktivního pizza day', async () => {
        await expect(addPizzaOrder(USER, PIZZA, SIZE)).rejects.toThrow('neexistuje');
    });

    test('vyhodí chybu pro pizza day ve stavu LOCKED', async () => {
        await seedPizzaDay(PizzaDayState.LOCKED);
        await expect(addPizzaOrder(USER, PIZZA, SIZE_M)).rejects.toThrow(PizzaDayState.CREATED);
    });
});

describe('removePizzaOrder', () => {
    test('odečte cenu a odstraní položku z objednávky', async () => {
        await seedPizzaDay();
        await addPizzaOrder(USER, PIZZA, SIZE_M);
        await addPizzaOrder(USER, { ...PIZZA, name: 'Diavola' }, SIZE_L);
        const variant = { varId: SIZE_M.varId, name: PIZZA.name, size: SIZE_M.size, price: SIZE_M.price };
        const data = await removePizzaOrder(USER, variant);
        const order = data.pizzaDay?.orders?.find(o => o.customer === USER);
        expect(order?.totalPrice).toBe(SIZE_L.price);
        expect(order?.pizzaList).toHaveLength(1);
    });

    test('odstraní celou objednávku, pokud je prázdná', async () => {
        await seedPizzaDay();
        await addPizzaOrder(USER, PIZZA, SIZE_M);
        const variant = { varId: SIZE_M.varId, name: PIZZA.name, size: SIZE_M.size, price: SIZE_M.price };
        const data = await removePizzaOrder(USER, variant);
        const order = data.pizzaDay?.orders?.find(o => o.customer === USER);
        expect(order).toBeUndefined();
    });
});

describe('removeAllUserPizzas', () => {
    test('odstraní objednávku uživatele', async () => {
        await seedPizzaDay();
        await addPizzaOrder(USER, PIZZA, SIZE);
        const data = await removeAllUserPizzas(USER);
        const order = data.pizzaDay?.orders?.find(o => o.customer === USER);
        expect(order).toBeUndefined();
    });

    test('je no-op bez pizza day', async () => {
        const data = await removeAllUserPizzas(USER);
        expect(data.pizzaDay).toBeUndefined();
    });
});

describe('updatePizzaFee', () => {
    test('přidá příplatek a přepočítá celkovou cenu', async () => {
        await seedPizzaDay();
        await addPizzaOrder(USER, PIZZA, SIZE_M);
        const data = await updatePizzaFee(CREATOR, USER, 'Balné', 20);
        const order = data.pizzaDay?.orders?.find(o => o.customer === USER);
        expect(order?.fee).toEqual({ text: 'Balné', price: 20 });
        expect(order?.totalPrice).toBe(SIZE_M.price + 20);
    });

    test('s cenou undefined odstraní příplatek', async () => {
        await seedPizzaDay();
        await addPizzaOrder(USER, PIZZA, SIZE_M);
        await updatePizzaFee(CREATOR, USER, 'Balné', 20);
        const data = await updatePizzaFee(CREATOR, USER, undefined, undefined);
        const order = data.pizzaDay?.orders?.find(o => o.customer === USER);
        expect(order?.fee).toBeUndefined();
        expect(order?.totalPrice).toBe(SIZE_M.price);
    });

    test('vyhodí chybu, pokud volá jiný uživatel než tvůrce', async () => {
        await seedPizzaDay();
        await addPizzaOrder(USER, PIZZA, SIZE_M);
        await expect(updatePizzaFee(USER, USER, 'Balné', 20)).rejects.toThrow('zakladatel');
    });
});

describe('lockPizzaDay / unlockPizzaDay', () => {
    test('tvůrce může zamknout pizza day', async () => {
        await seedPizzaDay();
        const data = await lockPizzaDay(CREATOR);
        expect(data.pizzaDay?.state).toBe(PizzaDayState.LOCKED);
    });

    test('jiný uživatel nemůže zamknout pizza day', async () => {
        await seedPizzaDay();
        await expect(lockPizzaDay(USER)).rejects.toThrow(USER);
    });

    test('zamčený pizza day lze odemknout', async () => {
        await seedPizzaDay();
        await lockPizzaDay(CREATOR);
        const data = await unlockPizzaDay(CREATOR);
        expect(data.pizzaDay?.state).toBe(PizzaDayState.CREATED);
    });

    test('nelze odemknout nezamčený pizza day', async () => {
        await seedPizzaDay();
        await expect(unlockPizzaDay(CREATOR)).rejects.toThrow(PizzaDayState.LOCKED);
    });
});

describe('finishPizzaOrder', () => {
    test('přesune pizza day do stavu ORDERED', async () => {
        await seedPizzaDay();
        await lockPizzaDay(CREATOR);
        const data = await finishPizzaOrder(CREATOR);
        expect(data.pizzaDay?.state).toBe(PizzaDayState.ORDERED);
    });

    test('vyhodí chybu v nesprávném stavu (CREATED)', async () => {
        await seedPizzaDay();
        await expect(finishPizzaOrder(CREATOR)).rejects.toThrow(PizzaDayState.LOCKED);
    });
});

describe('finishPizzaDelivery', () => {
    test('přesune pizza day do stavu DELIVERED', async () => {
        await seedPizzaDay();
        await lockPizzaDay(CREATOR);
        await finishPizzaOrder(CREATOR);
        const data = await finishPizzaDelivery(CREATOR);
        expect(data.pizzaDay?.state).toBe(PizzaDayState.DELIVERED);
    });

    test('vyhodí chybu v nesprávném stavu (LOCKED)', async () => {
        await seedPizzaDay();
        await lockPizzaDay(CREATOR);
        await expect(finishPizzaDelivery(CREATOR)).rejects.toThrow(PizzaDayState.ORDERED);
    });
});
