import { CashuWallet, CheckStateEnum } from "@cashu/cashu-ts";
import NDK, { NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import { mockNutzap } from "@nostr-dev-kit/ndk/test";
import { describe, expect, it, vi } from "vitest";
import { getProofSpendState } from "./spend-status";

// Create NDK with a signer
const ndkSigner = NDKPrivateKeySigner.generate();
const ndk = new NDK({ signer: ndkSigner });

describe("spend-status", () => {
    describe("getProofSpendState", () => {
        const wallet = new CashuWallet({} as any);

        it("should correctly map proofs to their spend states", async () => {
            const nutzaps = [
                await mockNutzap("https://mint1.com", 100, ndk, { senderPk: ndkSigner }),
                await mockNutzap("https://mint2.com", 200, ndk, { senderPk: ndkSigner }),
            ];

            wallet.checkProofsStates = vi.fn().mockResolvedValue([
                { state: CheckStateEnum.UNSPENT, Y: "1", witness: "1" }, // UNSPENT
                { state: CheckStateEnum.SPENT, Y: "1", witness: "1" }, // SPENT
            ]);

            const result = await getProofSpendState(wallet, nutzaps);

            expect(result.unspentProofs).toHaveLength(1);
            expect(result.spentProofs).toHaveLength(1);
            expect(result.nutzapsWithUnspentProofs).toHaveLength(1);
            expect(result.nutzapsWithSpentProofs).toHaveLength(1);
            expect(result.nutzapsWithUnspentProofs[0].id).toBe(nutzaps[0].id);
            expect(result.nutzapsWithSpentProofs[0].id).toBe(nutzaps[1].id);
        });

        it("should deduplicate proofs by C value", async () => {
            const nutzap = await mockNutzap("https://mint1.com", 100, ndk, { senderPk: ndkSigner });
            const duplicateProof = { ...nutzap.proofs[0] };

            nutzap.proofs.push(duplicateProof);

            wallet.checkProofsStates = vi
                .fn()
                .mockResolvedValue([{ state: CheckStateEnum.UNSPENT, Y: "1", witness: "1" }]);

            const result = await getProofSpendState(wallet, [nutzap]);

            expect(wallet.checkProofsStates).toHaveBeenCalledWith([nutzap.proofs[0]]);
            expect(result.unspentProofs).toHaveLength(1);
        });

        it("should handle empty nutzap array", async () => {
            const freshWallet = new CashuWallet({} as any);
            freshWallet.checkProofsStates = vi.fn().mockResolvedValue([]);

            const result = await getProofSpendState(freshWallet, []);

            expect(result.unspentProofs).toHaveLength(0);
            expect(result.spentProofs).toHaveLength(0);
            expect(result.nutzapsWithUnspentProofs).toHaveLength(0);
            expect(result.nutzapsWithSpentProofs).toHaveLength(0);
            expect(freshWallet.checkProofsStates).toHaveBeenCalledWith([]);
        });

        it("should handle nutzaps with empty proofs", async () => {
            const nutzap = await mockNutzap("https://mint1.com", 100, ndk, { senderPk: ndkSigner });
            nutzap.proofs = [];

            wallet.checkProofsStates = vi.fn().mockResolvedValue([]);

            const result = await getProofSpendState(wallet, [nutzap]);

            expect(result.unspentProofs).toHaveLength(0);
            expect(result.spentProofs).toHaveLength(0);
            expect(result.nutzapsWithUnspentProofs).toHaveLength(0);
            expect(result.nutzapsWithSpentProofs).toHaveLength(0);
            expect(wallet.checkProofsStates).toHaveBeenCalledWith([]);
        });

        it("should handle nutzaps with multiple proofs correctly", async () => {
            // Create a nutzap with multiple proofs
            const nutzap = await mockNutzap("https://mint1.com", 100, ndk, {
                senderPk: ndkSigner,
            });

            // Add a second proof
            const secondProof = {
                ...nutzap.proofs[0],
                C: "differentC",
                amount: 50,
            };
            nutzap.proofs.push(secondProof);

            // Mock checkProofsStates to return one spent and one unspent
            wallet.checkProofsStates = vi.fn().mockResolvedValue([
                { state: CheckStateEnum.UNSPENT, Y: "1", witness: "1" },
                { state: CheckStateEnum.SPENT, Y: "1", witness: "1" },
            ]);

            const result = await getProofSpendState(wallet, [nutzap]);

            // Both the spent and unspent arrays should have the same nutzap
            expect(result.unspentProofs).toHaveLength(1);
            expect(result.spentProofs).toHaveLength(1);

            // The nutzap should be in both spent and unspent arrays
            expect(result.nutzapsWithUnspentProofs).toHaveLength(1);
            expect(result.nutzapsWithSpentProofs).toHaveLength(1);
            expect(result.nutzapsWithUnspentProofs[0].id).toBe(nutzap.id);
            expect(result.nutzapsWithSpentProofs[0].id).toBe(nutzap.id);
        });

        it("should handle error responses from checkProofsStates gracefully", async () => {
            const nutzap = await mockNutzap("https://mint1.com", 100, ndk, {
                senderPk: ndkSigner,
            });

            // Mock checkProofsStates to throw an error
            wallet.checkProofsStates = vi.fn().mockRejectedValue(new Error("Mint server error"));

            // The function should gracefully handle errors
            await expect(getProofSpendState(wallet, [nutzap])).rejects.toThrow("Mint server error");
        });

        it("should process mixed nutzaps where some have spent and some have unspent proofs", async () => {
            const nutzaps = [
                await mockNutzap("https://mint1.com", 100, ndk, { senderPk: ndkSigner }),
                await mockNutzap("https://mint1.com", 200, ndk, { senderPk: ndkSigner }),
                await mockNutzap("https://mint1.com", 300, ndk, { senderPk: ndkSigner }),
            ];

            // Make unique 'C' values for each proof
            nutzaps[1].proofs[0].C = "uniqueC1";
            nutzaps[2].proofs[0].C = "uniqueC2";

            wallet.checkProofsStates = vi.fn().mockResolvedValue([
                { state: CheckStateEnum.UNSPENT, Y: "1", witness: "1" }, // UNSPENT
                { state: CheckStateEnum.SPENT, Y: "1", witness: "1" }, // SPENT
                { state: CheckStateEnum.UNSPENT, Y: "1", witness: "1" }, // UNSPENT
            ]);

            const result = await getProofSpendState(wallet, nutzaps);

            expect(result.unspentProofs).toHaveLength(2);
            expect(result.spentProofs).toHaveLength(1);
            expect(result.nutzapsWithUnspentProofs).toHaveLength(2);
            expect(result.nutzapsWithSpentProofs).toHaveLength(1);

            // The first and third nutzaps should be unspent
            expect(result.nutzapsWithUnspentProofs.map((n) => n.id)).toContain(nutzaps[0].id);
            expect(result.nutzapsWithUnspentProofs.map((n) => n.id)).toContain(nutzaps[2].id);

            // The second nutzap should be spent
            expect(result.nutzapsWithSpentProofs.map((n) => n.id)).toContain(nutzaps[1].id);
        });
    });
});
