import { describe, it, expect, beforeAll, afterAll, beforeEach } from "bun:test";
import { DrizzleClient } from "@shared/db/DrizzleClient";
import { users, transactions } from "@db/schema";
import { eq } from "drizzle-orm";
import { economyService } from "./economy.service";

// Use high IDs to avoid conflicts with real data
const SENDER_ID = "999000000000000001";
const RECEIVER_ID = "999000000000000002";

// This test requires a real database connection.
// It is excluded from `bun test` by default (*.integration.test.ts pattern)
// and only runs in CI with `--integration` flag, which provides PostgreSQL.

async function cleanupTestUsers() {
    // transactions.userId has onDelete: 'cascade', so deleting users removes their transactions
    await DrizzleClient.delete(users).where(eq(users.id, BigInt(SENDER_ID)));
    await DrizzleClient.delete(users).where(eq(users.id, BigInt(RECEIVER_ID)));
}

describe("Economy Integration Tests", () => {
    beforeAll(async () => {
        await cleanupTestUsers();

        await DrizzleClient.insert(users).values([
            { id: BigInt(SENDER_ID), username: "test_sender_integration", balance: 1000n },
            { id: BigInt(RECEIVER_ID), username: "test_receiver_integration", balance: 500n },
        ]);
    });

    beforeEach(async () => {
        // Reset balances and clear transaction history before each test
        await DrizzleClient.delete(transactions).where(
            eq(transactions.userId, BigInt(SENDER_ID))
        );
        await DrizzleClient.delete(transactions).where(
            eq(transactions.userId, BigInt(RECEIVER_ID))
        );
        await DrizzleClient.update(users)
            .set({ balance: 1000n })
            .where(eq(users.id, BigInt(SENDER_ID)));
        await DrizzleClient.update(users)
            .set({ balance: 500n })
            .where(eq(users.id, BigInt(RECEIVER_ID)));
    });

    afterAll(async () => {
        await cleanupTestUsers();
    });

    describe("transfer", () => {
        it("should debit sender, credit receiver, and create transaction records", async () => {
            const result = await economyService.transfer(SENDER_ID, RECEIVER_ID, 200n);

            expect(result.success).toBe(true);
            expect(result.amount).toBe(200n);

            const sender = await DrizzleClient.query.users.findFirst({
                where: eq(users.id, BigInt(SENDER_ID)),
            });
            const receiver = await DrizzleClient.query.users.findFirst({
                where: eq(users.id, BigInt(RECEIVER_ID)),
            });

            expect(sender!.balance).toBe(800n);
            expect(receiver!.balance).toBe(700n);

            const senderTxs = await DrizzleClient.query.transactions.findMany({
                where: eq(transactions.userId, BigInt(SENDER_ID)),
            });
            const receiverTxs = await DrizzleClient.query.transactions.findMany({
                where: eq(transactions.userId, BigInt(RECEIVER_ID)),
            });

            expect(senderTxs.length).toBe(1);
            expect(senderTxs[0]!.amount).toBe(-200n);
            expect(senderTxs[0]!.type).toBe("TRANSFER_OUT");

            expect(receiverTxs.length).toBe(1);
            expect(receiverTxs[0]!.amount).toBe(200n);
            expect(receiverTxs[0]!.type).toBe("TRANSFER_IN");
        });

        it("should reject transfer with insufficient funds and leave balances unchanged", async () => {
            await expect(
                economyService.transfer(SENDER_ID, RECEIVER_ID, 2000n)
            ).rejects.toThrow("Insufficient funds");

            // Verify balances unchanged
            const sender = await DrizzleClient.query.users.findFirst({
                where: eq(users.id, BigInt(SENDER_ID)),
            });
            const receiver = await DrizzleClient.query.users.findFirst({
                where: eq(users.id, BigInt(RECEIVER_ID)),
            });
            expect(sender!.balance).toBe(1000n);
            expect(receiver!.balance).toBe(500n);

            // Verify no transaction records were created for either party
            const senderTxs = await DrizzleClient.query.transactions.findMany({
                where: eq(transactions.userId, BigInt(SENDER_ID)),
            });
            const receiverTxs = await DrizzleClient.query.transactions.findMany({
                where: eq(transactions.userId, BigInt(RECEIVER_ID)),
            });
            expect(senderTxs.length).toBe(0);
            expect(receiverTxs.length).toBe(0);
        });

        it("should reject a self-transfer", async () => {
            await expect(
                economyService.transfer(SENDER_ID, SENDER_ID, 100n)
            ).rejects.toThrow("Cannot transfer to self");
        });

        it("should reject a non-positive amount", async () => {
            await expect(
                economyService.transfer(SENDER_ID, RECEIVER_ID, 0n)
            ).rejects.toThrow("Amount must be positive");

            await expect(
                economyService.transfer(SENDER_ID, RECEIVER_ID, -100n)
            ).rejects.toThrow("Amount must be positive");
        });

        it("should apply multiple sequential transfers correctly", async () => {
            await economyService.transfer(SENDER_ID, RECEIVER_ID, 100n);
            await economyService.transfer(SENDER_ID, RECEIVER_ID, 200n);
            await economyService.transfer(RECEIVER_ID, SENDER_ID, 50n);

            const sender = await DrizzleClient.query.users.findFirst({
                where: eq(users.id, BigInt(SENDER_ID)),
            });
            const receiver = await DrizzleClient.query.users.findFirst({
                where: eq(users.id, BigInt(RECEIVER_ID)),
            });

            // Sender: 1000 - 100 - 200 + 50 = 750
            expect(sender!.balance).toBe(750n);
            // Receiver: 500 + 100 + 200 - 50 = 750
            expect(receiver!.balance).toBe(750n);
        });
    });
});
