import type { TradeSession, TradeParticipant } from "@/modules/trade/trade.types";
import { economyService } from "@shared/modules/economy/economy.service";
import { inventoryService } from "@shared/modules/inventory/inventory.service";
import { itemTransactions } from "@db/schema";
import { withTransaction } from "@/lib/db";
import type { Transaction } from "@shared/lib/types";
import { TransactionType, ItemTransactionType } from "@shared/lib/constants";
import { UserError } from "@shared/lib/errors";

// Module-level session storage
const sessions = new Map<string, TradeSession>();

/**
 * Unlocks both participants in a trade session
 */
const unlockAll = (session: TradeSession) => {
    session.userA.locked = false;
    session.userB.locked = false;
};

/**
 * Processes a one-way transfer from one participant to another
 */
const processTransfer = async (tx: Transaction, from: TradeParticipant, to: TradeParticipant, threadId: string) => {
    // 1. Money
    if (from.offer.money > 0n) {
        await economyService.modifyUserBalance(
            from.id,
            -from.offer.money,
            TransactionType.TRADE_OUT,
            `Trade with ${to.username} (Thread: ${threadId})`,
            to.id,
            tx
        );
        await economyService.modifyUserBalance(
            to.id,
            from.offer.money,
            TransactionType.TRADE_IN,
            `Trade with ${from.username} (Thread: ${threadId})`,
            from.id,
            tx
        );
    }

    // 2. Items
    for (const item of from.offer.items) {
        // Remove from sender
        await inventoryService.removeItem(from.id, item.id, item.quantity, tx);

        // Add to receiver
        await inventoryService.addItem(to.id, item.id, item.quantity, tx);

        // Log Item Transaction (Sender)
        await tx.insert(itemTransactions).values({
            userId: BigInt(from.id),
            relatedUserId: BigInt(to.id),
            itemId: item.id,
            quantity: -item.quantity,
            type: ItemTransactionType.TRADE_OUT,
            description: `Traded to ${to.username}`,
        });

        // Log Item Transaction (Receiver)
        await tx.insert(itemTransactions).values({
            userId: BigInt(to.id),
            relatedUserId: BigInt(from.id),
            itemId: item.id,
            quantity: item.quantity,
            type: ItemTransactionType.TRADE_IN,
            description: `Received from ${from.username}`,
        });
    }
};

export const tradeService = {
    // Expose for testing
    _sessions: sessions,
    /**
     * Creates a new trade session
     */
    createSession: (threadId: string, userA: { id: string, username: string }, userB: { id: string, username: string }): TradeSession => {
        const session: TradeSession = {
            threadId,
            userA: {
                id: userA.id,
                username: userA.username,
                locked: false,
                offer: { money: 0n, items: [] }
            },
            userB: {
                id: userB.id,
                username: userB.username,
                locked: false,
                offer: { money: 0n, items: [] }
            },
            state: 'NEGOTIATING',
            lastInteraction: Date.now()
        };

        sessions.set(threadId, session);
        return session;
    },

    /** Get an active trade session by thread ID. */
    getSession: (threadId: string): TradeSession | undefined => {
        return sessions.get(threadId);
    },

    /** Remove a trade session from memory. */
    endSession: (threadId: string) => {
        sessions.delete(threadId);
    },

    /**
     * Updates an offer. If allowed, validation checks should be done BEFORE calling this.
     * unlocking logic is handled here (if offer changes, unlock both).
     */
    updateMoney: (threadId: string, userId: string, amount: bigint) => {
        const session = tradeService.getSession(threadId);
        if (!session) throw new UserError("Session not found");
        if (session.state !== 'NEGOTIATING') throw new UserError("Trade is not active");

        const participant = session.userA.id === userId ? session.userA : session.userB.id === userId ? session.userB : null;
        if (!participant) throw new UserError("User not in trade");

        participant.offer.money = amount;
        unlockAll(session);
        session.lastInteraction = Date.now();
    },

    /** Add an item to a participant's trade offer; unlocks both sides when the offer changes. */
    addItem: (threadId: string, userId: string, item: { id: number, name: string }, quantity: bigint) => {
        const session = tradeService.getSession(threadId);
        if (!session) throw new UserError("Session not found");
        if (session.state !== 'NEGOTIATING') throw new UserError("Trade is not active");

        const participant = session.userA.id === userId ? session.userA : session.userB.id === userId ? session.userB : null;
        if (!participant) throw new UserError("User not in trade");

        const existing = participant.offer.items.find(i => i.id === item.id);
        if (existing) {
            existing.quantity += quantity;
        } else {
            participant.offer.items.push({ id: item.id, name: item.name, quantity });
        }

        unlockAll(session);
        session.lastInteraction = Date.now();
    },

    /** Remove an item from a participant's trade offer; unlocks both sides. */
    removeItem: (threadId: string, userId: string, itemId: number) => {
        const session = tradeService.getSession(threadId);
        if (!session) throw new UserError("Session not found");

        const participant = session.userA.id === userId ? session.userA : session.userB.id === userId ? session.userB : null;
        if (!participant) throw new UserError("User not in trade");

        participant.offer.items = participant.offer.items.filter(i => i.id !== itemId);

        unlockAll(session);
        session.lastInteraction = Date.now();
    },

    /** Toggle a participant's lock status on their offer; returns the new lock state. */
    toggleLock: (threadId: string, userId: string): boolean => {
        const session = tradeService.getSession(threadId);
        if (!session) throw new UserError("Session not found");

        const participant = session.userA.id === userId ? session.userA : session.userB.id === userId ? session.userB : null;
        if (!participant) throw new UserError("User not in trade");

        participant.locked = !participant.locked;
        session.lastInteraction = Date.now();

        return participant.locked;
    },

    /**
     * Executes the trade atomically.
     * 1. Validates balances/inventory for both users.
     * 2. Swaps money.
     * 3. Swaps items.
     * 4. Logs transactions.
     */
    executeTrade: async (threadId: string): Promise<void> => {
        const session = tradeService.getSession(threadId);
        if (!session) throw new UserError("Session not found");

        if (!session.userA.locked || !session.userB.locked) {
            throw new UserError("Both players must accept the trade first.");
        }

        session.state = 'COMPLETED'; // Prevent double execution

        await withTransaction(async (tx) => {
            // -- Validate & Execute User A -> User B --
            await processTransfer(tx, session.userA, session.userB, session.threadId);

            // -- Validate & Execute User B -> User A --
            await processTransfer(tx, session.userB, session.userA, session.threadId);
        });

        tradeService.endSession(threadId);
    },

    /** Clear all active trade sessions from memory. */
    clearSessions: () => {
        sessions.clear();
        console.log("[TradeService] All active trade sessions cleared.");
    }
};
