import { describe, test, expect, afterAll, beforeAll, mock } from "bun:test";
import type { WebServerInstance } from "./server";
import { createWebServer } from "./server";

/**
 * Items API Integration Tests
 * 
 * Tests the full CRUD functionality for the Items management API.
 * Uses mocked database and service layers.
 */

// --- Mock Types ---
interface MockItem {
    id: number;
    name: string;
    description: string | null;
    rarity: string;
    type: string;
    price: bigint | null;
    iconUrl: string;
    imageUrl: string;
    usageData: { consume: boolean; effects: any[] } | null;
}

// --- Mock Data ---
let mockItems: MockItem[] = [
    {
        id: 1,
        name: "Health Potion",
        description: "Restores health",
        rarity: "C",
        type: "CONSUMABLE",
        price: 100n,
        iconUrl: "/assets/items/1.png",
        imageUrl: "/assets/items/1.png",
        usageData: { consume: true, effects: [] },
    },
    {
        id: 2,
        name: "Iron Sword",
        description: "A basic sword",
        rarity: "R",
        type: "EQUIPMENT",
        price: 500n,
        iconUrl: "/assets/items/2.png",
        imageUrl: "/assets/items/2.png",
        usageData: null,
    },
];

let mockIdCounter = 3;

// --- Mock Items Service ---
mock.module("@shared/modules/items/items.service", () => ({
    itemsService: {
        getAllItems: mock(async (filters: any = {}) => {
            let filtered = [...mockItems];

            if (filters.search) {
                const search = filters.search.toLowerCase();
                filtered = filtered.filter(
                    (item) =>
                        item.name.toLowerCase().includes(search) ||
                        (item.description?.toLowerCase().includes(search) ?? false)
                );
            }

            if (filters.type) {
                filtered = filtered.filter((item) => item.type === filters.type);
            }

            if (filters.rarity) {
                filtered = filtered.filter((item) => item.rarity === filters.rarity);
            }

            return {
                items: filtered,
                total: filtered.length,
            };
        }),

        getItemById: mock(async (id: number) => {
            return mockItems.find((item) => item.id === id) ?? null;
        }),

        isNameTaken: mock(async (name: string, excludeId?: number) => {
            return mockItems.some(
                (item) =>
                    item.name.toLowerCase() === name.toLowerCase() &&
                    item.id !== excludeId
            );
        }),

        createItem: mock(async (data: any) => {
            const newItem: MockItem = {
                id: mockIdCounter++,
                name: data.name,
                description: data.description ?? null,
                rarity: data.rarity ?? "C",
                type: data.type,
                price: data.price ?? null,
                iconUrl: data.iconUrl,
                imageUrl: data.imageUrl,
                usageData: data.usageData ?? null,
            };
            mockItems.push(newItem);
            return newItem;
        }),

        updateItem: mock(async (id: number, data: any) => {
            const index = mockItems.findIndex((item) => item.id === id);
            if (index === -1) return null;

            mockItems[index] = { ...mockItems[index], ...data };
            return mockItems[index];
        }),

        deleteItem: mock(async (id: number) => {
            const index = mockItems.findIndex((item) => item.id === id);
            if (index === -1) return null;

            const [deleted] = mockItems.splice(index, 1);
            return deleted;
        }),
    },
}));

// --- Mock Utilities ---
mock.module("@shared/lib/utils", () => ({
    deepMerge: (target: any, source: any) => ({ ...target, ...source }),
    jsonReplacer: (key: string, value: any) =>
        typeof value === "bigint" ? value.toString() : value,
}));

// --- Mock Auth (bypass authentication) ---
mock.module("./routes/auth.routes", () => ({
    authRoutes: { name: "auth", handler: () => null },
    isAuthenticated: () => true,
    getSession: () => ({ discordId: "123", username: "testuser", expiresAt: Date.now() + 3600000 }),
}));

// --- Mock Logger ---
mock.module("@shared/lib/logger", () => ({
    logger: {
        info: () => { },
        warn: () => { },
        error: () => { },
        debug: () => { },
    },
}));

describe("Items API", () => {
    const port = 3002;
    const hostname = "127.0.0.1";
    const baseUrl = `http://${hostname}:${port}`;
    let serverInstance: WebServerInstance | null = null;

    beforeAll(async () => {
        // Reset mock data before all tests
        mockItems = [
            {
                id: 1,
                name: "Health Potion",
                description: "Restores health",
                rarity: "C",
                type: "CONSUMABLE",
                price: 100n,
                iconUrl: "/assets/items/1.png",
                imageUrl: "/assets/items/1.png",
                usageData: { consume: true, effects: [] },
            },
            {
                id: 2,
                name: "Iron Sword",
                description: "A basic sword",
                rarity: "R",
                type: "EQUIPMENT",
                price: 500n,
                iconUrl: "/assets/items/2.png",
                imageUrl: "/assets/items/2.png",
                usageData: null,
            },
        ];
        mockIdCounter = 3;

        serverInstance = await createWebServer({ port, hostname });
    });

    afterAll(async () => {
        if (serverInstance) {
            await serverInstance.stop();
        }
    });

    // ===========================================
    // GET /api/items Tests
    // ===========================================
    describe("GET /api/items", () => {
        test("should return all items", async () => {
            const response = await fetch(`${baseUrl}/api/items`);
            expect(response.status).toBe(200);

            const data = (await response.json()) as { items: MockItem[]; total: number };
            expect(data.items).toBeInstanceOf(Array);
            expect(data.total).toBeGreaterThanOrEqual(0);
        });

        test("should filter items by search query", async () => {
            const response = await fetch(`${baseUrl}/api/items?search=potion`);
            expect(response.status).toBe(200);

            const data = (await response.json()) as { items: MockItem[]; total: number };
            expect(data.items.every((item) =>
                item.name.toLowerCase().includes("potion") ||
                (item.description?.toLowerCase().includes("potion") ?? false)
            )).toBe(true);
        });

        test("should filter items by type", async () => {
            const response = await fetch(`${baseUrl}/api/items?type=CONSUMABLE`);
            expect(response.status).toBe(200);

            const data = (await response.json()) as { items: MockItem[]; total: number };
            expect(data.items.every((item) => item.type === "CONSUMABLE")).toBe(true);
        });

        test("should filter items by rarity", async () => {
            const response = await fetch(`${baseUrl}/api/items?rarity=C`);
            expect(response.status).toBe(200);

            const data = (await response.json()) as { items: MockItem[]; total: number };
            expect(data.items.every((item) => item.rarity === "C")).toBe(true);
        });
    });

    // ===========================================
    // GET /api/items/:id Tests
    // ===========================================
    describe("GET /api/items/:id", () => {
        test("should return a single item by ID", async () => {
            const response = await fetch(`${baseUrl}/api/items/1`);
            expect(response.status).toBe(200);

            const data = (await response.json()) as MockItem;
            expect(data.id).toBe(1);
            expect(data.name).toBe("Health Potion");
        });

        test("should return 404 for non-existent item", async () => {
            const response = await fetch(`${baseUrl}/api/items/9999`);
            expect(response.status).toBe(404);

            const data = (await response.json()) as { error: string };
            expect(data.error).toBe("Item not found");
        });
    });

    // ===========================================
    // POST /api/items Tests
    // ===========================================
    describe("POST /api/items", () => {
        test("should create a new item", async () => {
            const newItem = {
                name: "Magic Staff",
                description: "A powerful staff",
                rarity: "SR",
                type: "EQUIPMENT",
                price: "1000",
                iconUrl: "/assets/items/placeholder.png",
                imageUrl: "/assets/items/placeholder.png",
            };

            const response = await fetch(`${baseUrl}/api/items`, {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify(newItem),
            });

            expect(response.status).toBe(201);

            const data = (await response.json()) as { success: boolean; item: MockItem };
            expect(data.success).toBe(true);
            expect(data.item.name).toBe("Magic Staff");
            expect(data.item.id).toBeGreaterThan(0);
        });

        test("should reject item without required fields", async () => {
            const response = await fetch(`${baseUrl}/api/items`, {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({ description: "No name or type" }),
            });

            expect(response.status).toBe(400);

            const data = (await response.json()) as { error: string };
            expect(data.error).toContain("required");
        });

        test("should reject duplicate item name", async () => {
            const response = await fetch(`${baseUrl}/api/items`, {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({
                    name: "Health Potion", // Already exists
                    type: "CONSUMABLE",
                    iconUrl: "/assets/items/placeholder.png",
                    imageUrl: "/assets/items/placeholder.png",
                }),
            });

            expect(response.status).toBe(409);

            const data = (await response.json()) as { error: string };
            expect(data.error).toContain("already exists");
        });
    });

    // ===========================================
    // PUT /api/items/:id Tests
    // ===========================================
    describe("PUT /api/items/:id", () => {
        test("should update an existing item", async () => {
            const response = await fetch(`${baseUrl}/api/items/1`, {
                method: "PUT",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({
                    description: "Updated description",
                    price: "200",
                }),
            });

            expect(response.status).toBe(200);

            const data = (await response.json()) as { success: boolean; item: MockItem };
            expect(data.success).toBe(true);
            expect(data.item.description).toBe("Updated description");
        });

        test("should return 404 for updating non-existent item", async () => {
            const response = await fetch(`${baseUrl}/api/items/9999`, {
                method: "PUT",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({ name: "New Name" }),
            });

            expect(response.status).toBe(404);
        });

        test("should reject duplicate name when updating", async () => {
            const response = await fetch(`${baseUrl}/api/items/2`, {
                method: "PUT",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({
                    name: "Health Potion", // ID 1 has this name
                }),
            });

            expect(response.status).toBe(409);
        });
    });

    // ===========================================
    // DELETE /api/items/:id Tests
    // ===========================================
    describe("DELETE /api/items/:id", () => {
        test("should delete an existing item", async () => {
            // First, create an item to delete
            const createResponse = await fetch(`${baseUrl}/api/items`, {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({
                    name: "Item to Delete",
                    type: "MATERIAL",
                    iconUrl: "/assets/items/placeholder.png",
                    imageUrl: "/assets/items/placeholder.png",
                }),
            });

            const { item } = (await createResponse.json()) as { item: MockItem };

            // Now delete it
            const deleteResponse = await fetch(`${baseUrl}/api/items/${item.id}`, {
                method: "DELETE",
            });

            expect(deleteResponse.status).toBe(204);

            // Verify it's gone
            const getResponse = await fetch(`${baseUrl}/api/items/${item.id}`);
            expect(getResponse.status).toBe(404);
        });

        test("should return 404 for deleting non-existent item", async () => {
            const response = await fetch(`${baseUrl}/api/items/9999`, {
                method: "DELETE",
            });

            expect(response.status).toBe(404);
        });
    });

    // ===========================================
    // Static Asset Serving Tests
    // ===========================================
    describe("Static Asset Serving (/assets/*)", () => {
        test("should return 404 for non-existent asset", async () => {
            const response = await fetch(`${baseUrl}/assets/items/nonexistent.png`);
            expect(response.status).toBe(404);
        });

        test("should prevent path traversal attacks", async () => {
            // Note: fetch() and HTTP servers normalize ".." segments before the handler sees them,
            // so we can't send raw traversal paths over HTTP. Instead, test that a suspicious
            // asset path (with encoded sequences) doesn't serve sensitive file content.
            const response = await fetch(`${baseUrl}/assets/..%2f..%2f..%2fetc%2fpasswd`);
            // Should not serve actual file content — expect 403 or 404
            expect([403, 404]).toContain(response.status);
        });
    });

    // ===========================================
    // Validation Edge Cases
    // ===========================================
    describe("Validation Edge Cases", () => {
        test("should handle empty search query gracefully", async () => {
            const response = await fetch(`${baseUrl}/api/items?search=`);
            expect(response.status).toBe(200);
        });

        test("should handle invalid pagination values", async () => {
            const response = await fetch(`${baseUrl}/api/items?limit=abc&offset=xyz`);
            // Should not crash, may use defaults
            expect(response.status).toBe(200);
        });

        test("should handle missing content-type header", async () => {
            const response = await fetch(`${baseUrl}/api/items`, {
                method: "POST",
                body: JSON.stringify({ name: "Test", type: "MATERIAL" }),
            });
            // May fail due to no content-type, but shouldn't crash
            expect([200, 201, 400, 415]).toContain(response.status);
        });
    });
});
