import crypto from "crypto";
import getStorage from "./storage";
import { getClientData, getToday, initIfNeeded } from "./service";
import { getStores } from "./stores";
import { removePendingQrsByGroupId } from "./pizza";
import { ClientData, GroupState, MealSlot, OrderGroup, OrderGroupMember } from "../../types/gen/types.gen";
import { formatDate } from "./utils";

const storage = getStorage();

async function getExtraData(date?: Date): Promise<ClientData> {
    await initIfNeeded(date, MealSlot.EXTRA);
    const data = await getClientData(date, MealSlot.EXTRA);
    data.stores = await getStores();
    return data;
}

function getExtraKey(date?: Date): string {
    return `${formatDate(date ?? getToday())}_extra`;
}

async function saveExtraData(data: ClientData, date?: Date): Promise<ClientData> {
    await storage.setData(getExtraKey(date), data);
    return data;
}

function findGroup(data: ClientData, id: string): OrderGroup | undefined {
    return data.groups?.find(g => g.id === id);
}

export async function createGroup(creatorLogin: string, name: string, date?: Date): Promise<ClientData> {
    const stores = await getStores();
    if (!stores.some(s => s.toLowerCase() === name.trim().toLowerCase())) {
        throw new Error('Obchod není v seznamu povolených obchodů');
    }
    const data = await getExtraData(date);
    const canonical = stores.find(s => s.toLowerCase() === name.trim().toLowerCase())!;
    const group: OrderGroup = {
        id: crypto.randomUUID(),
        name: canonical,
        creatorLogin,
        state: GroupState.OPEN,
        members: { [creatorLogin]: {} },
    };
    data.groups = [...(data.groups ?? []), group];
    return saveExtraData(data, date);
}

export async function deleteGroup(login: string, groupId: string, date?: Date): Promise<ClientData> {
    const data = await getExtraData(date);
    const group = findGroup(data, groupId);
    if (!group) throw new Error('Skupina nebyla nalezena');
    if (group.creatorLogin !== login) throw new Error('Skupinu může smazat pouze zakladatel');
    data.groups = (data.groups ?? []).filter(g => g.id !== groupId);
    return saveExtraData(data, date);
}

export async function addGroupMember(login: string, groupId: string, targetLogin: string, date?: Date): Promise<ClientData> {
    const data = await getExtraData(date);
    const group = findGroup(data, groupId);
    if (!group) throw new Error('Skupina nebyla nalezena');
    if (group.state === GroupState.ORDERED) throw new Error('Skupinu ve stavu "objednáno" nelze upravovat');
    if (login !== group.creatorLogin && login !== targetLogin) {
        throw new Error('Přidat jiného uživatele může pouze zakladatel');
    }
    if (group.state === GroupState.LOCKED && login !== group.creatorLogin) {
        throw new Error('Skupinu ve stavu "uzamčeno" může upravovat pouze zakladatel');
    }
    if (group.members[targetLogin]) throw new Error('Uživatel je již ve skupině');
    group.members[targetLogin] = {};
    return saveExtraData(data, date);
}

export async function removeGroupMember(login: string, groupId: string, targetLogin: string, date?: Date): Promise<ClientData> {
    const data = await getExtraData(date);
    const group = findGroup(data, groupId);
    if (!group) throw new Error('Skupina nebyla nalezena');
    if (group.state === GroupState.ORDERED) throw new Error('Skupinu ve stavu "objednáno" nelze upravovat');
    if (login !== group.creatorLogin && login !== targetLogin) {
        throw new Error('Odebrat jiného uživatele může pouze zakladatel');
    }
    if (group.state === GroupState.LOCKED && login !== group.creatorLogin) {
        throw new Error('Skupinu ve stavu "uzamčeno" může upravovat pouze zakladatel');
    }
    if (targetLogin === group.creatorLogin) throw new Error('Zakladatel skupiny nemůže být odebrán');
    if (!group.members[targetLogin]) throw new Error('Uživatel není ve skupině');
    delete group.members[targetLogin];
    return saveExtraData(data, date);
}

export async function updateGroupMember(login: string, groupId: string, targetLogin: string, patch: Partial<OrderGroupMember>, date?: Date): Promise<ClientData> {
    const data = await getExtraData(date);
    const group = findGroup(data, groupId);
    if (!group) throw new Error('Skupina nebyla nalezena');
    if (group.state === GroupState.ORDERED) throw new Error('Skupinu ve stavu "objednáno" nelze upravovat');
    const isSelf = login === targetLogin;
    const isCreator = login === group.creatorLogin;
    if (!isSelf && !isCreator) throw new Error('Upravit jiného uživatele může pouze zakladatel');
    if (!isCreator && group.state === GroupState.LOCKED) {
        throw new Error('Skupinu ve stavu "uzamčeno" může upravovat pouze zakladatel');
    }
    if (!group.members[targetLogin]) throw new Error('Uživatel není ve skupině');
    group.members[targetLogin] = { ...group.members[targetLogin], ...patch };
    return saveExtraData(data, date);
}

const VALID_TRANSITIONS: Record<GroupState, GroupState[]> = {
    [GroupState.OPEN]: [GroupState.LOCKED],
    [GroupState.LOCKED]: [GroupState.OPEN, GroupState.ORDERED],
    [GroupState.ORDERED]: [GroupState.LOCKED],
};

function getCurrentHHMM(): string {
    const now = new Date();
    return `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
}

export async function setGroupState(login: string, groupId: string, newState: GroupState, date?: Date): Promise<ClientData> {
    const data = await getExtraData(date);
    const group = findGroup(data, groupId);
    if (!group) throw new Error('Skupina nebyla nalezena');
    if (group.creatorLogin !== login) throw new Error('Stav může měnit pouze zakladatel');
    if (!VALID_TRANSITIONS[group.state].includes(newState)) {
        throw new Error(`Nelze přejít ze stavu "${group.state}" do stavu "${newState}"`);
    }
    if (newState === GroupState.ORDERED) {
        group.orderedAt = getCurrentHHMM();
    }
    if (group.state === GroupState.ORDERED && newState === GroupState.LOCKED) {
        const memberLogins = Object.keys(group.members);
        await removePendingQrsByGroupId(memberLogins, groupId);
        group.orderedAt = undefined;
        group.deliveryAt = undefined;
        for (const ml of memberLogins) {
            group.members[ml] = { ...group.members[ml], paid: undefined };
        }
    }
    group.state = newState;
    return saveExtraData(data, date);
}

export async function markGroupMemberPaid(login: string, groupId: string, date?: Date): Promise<ClientData | null> {
    const data = await getExtraData(date);
    const group = findGroup(data, groupId);
    if (!group || !group.members[login]) return null;
    group.members[login] = { ...group.members[login], paid: true };
    return saveExtraData(data, date);
}

export async function updateGroupFees(login: string, groupId: string, fees?: number, shipping?: number, tip?: number, discountType?: string, discountValue?: number, date?: Date): Promise<ClientData> {
    const data = await getExtraData(date);
    const group = findGroup(data, groupId);
    if (!group) throw new Error('Skupina nebyla nalezena');
    if (group.creatorLogin !== login) throw new Error('Poplatky může měnit pouze zakladatel');
    if (group.state === GroupState.ORDERED) throw new Error('Skupinu ve stavu "objednáno" nelze upravovat');
    if (fees !== undefined) group.fees = fees > 0 ? fees : undefined;
    if (shipping !== undefined) group.shipping = shipping > 0 ? shipping : undefined;
    if (tip !== undefined) group.tip = tip > 0 ? tip : undefined;
    if (discountType !== undefined) group.discountType = (discountType as any) || undefined;
    if (discountValue !== undefined) group.discountValue = discountValue > 0 ? discountValue : undefined;
    return saveExtraData(data, date);
}

export async function updateGroupTimes(login: string, groupId: string, orderedAt?: string, deliveryAt?: string, date?: Date): Promise<ClientData> {
    const data = await getExtraData(date);
    const group = findGroup(data, groupId);
    if (!group) throw new Error('Skupina nebyla nalezena');
    if (group.creatorLogin !== login) throw new Error('Časy může měnit pouze zakladatel');
    if (orderedAt !== undefined) group.orderedAt = orderedAt || undefined;
    if (deliveryAt !== undefined) group.deliveryAt = deliveryAt || undefined;
    return saveExtraData(data, date);
}
