import { renderHook, act } from "@testing-library/react";
import { useOverageSettings } from "../useOverageSettings";
import { AccountDashboardApiContext } from "../../context/account-dashboard-api-context";

import { ReactNode } from "react";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { mockUsageLimitsData } from "./mockUsageLimitsData";
import {
	EnableOveragesResponse,
	ResourceType,
	UsageLimits,
} from "../../types/account-dashboard-types";

// Mock API functions
const createMockApi = (
	enableOveragesResponse?:
		| EnableOveragesResponse
		| Promise<EnableOveragesResponse>
) => ({
	signOut: vi.fn(),
	signIn: vi.fn(),
	executeCommand: vi.fn(),
	getUsageLimits: vi.fn(),
	enableOverages: vi
		.fn()
		.mockResolvedValue(enableOveragesResponse || { data: true, error: null }),
	getPaymentPortalUrl: vi.fn(),
	close: vi.fn(),
	showSubscriptionPlans: vi.fn(),
});

// Test wrapper component
const createWrapper =
	(api: any) =>
	({ children }: { children: ReactNode }) =>
		(
			<AccountDashboardApiContext.Provider value={api}>
				{children}
			</AccountDashboardApiContext.Provider>
		);

describe("useOverageSettings", () => {
	beforeEach(() => {
		vi.clearAllMocks();
	});

	describe("initialization", () => {
		it("should initialize with default values when no usage limits provided", () => {
			const mockApi = createMockApi();
			const wrapper = createWrapper(mockApi);

			const { result } = renderHook(
				() => useOverageSettings({ usageLimits: null }),
				{ wrapper }
			);

			expect(result.current.overageEnabled).toBe(false);
			expect(result.current.updating).toBe(false);
			expect(result.current.resourceToOverageMap.size).toBe(0);
			expect(result.current.shouldShowOverageTable).toBe(false);
			expect(typeof result.current.formatCurrency).toBe("function");
			expect(typeof result.current.generateOverageText).toBe("function");
		});

		it("should initialize overage enabled state from usage limits", () => {
			const mockApi = createMockApi();
			const wrapper = createWrapper(mockApi);

			const { result } = renderHook(
				() =>
					useOverageSettings({
						usageLimits: mockUsageLimitsData.paidUserWithOverages.data,
					}),
				{ wrapper }
			);

			expect(result.current.overageEnabled).toBe(true);
			expect(result.current.updating).toBe(false);
		});

		it("should initialize with disabled overages for free user", () => {
			const mockApi = createMockApi();
			const wrapper = createWrapper(mockApi);

			const { result } = renderHook(
				() =>
					useOverageSettings({
						usageLimits: mockUsageLimitsData.freeUserLowUsage.data,
					}),
				{ wrapper }
			);

			expect(result.current.overageEnabled).toBe(false);
		});
	});

	describe("overage data processing", () => {
		it("should process overage data correctly for user with overages", () => {
			const mockApi = createMockApi();
			const wrapper = createWrapper(mockApi);

			const { result } = renderHook(
				() =>
					useOverageSettings({
						usageLimits: mockUsageLimitsData.paidUserWithOverages.data,
					}),
				{ wrapper }
			);

			const resourceToOverageMap = result.current.resourceToOverageMap;

			// Should have entries for each resource type with overages
			expect(resourceToOverageMap.has("SPEC")).toBe(true);
			expect(resourceToOverageMap.has("VIBE")).toBe(true);
			expect(resourceToOverageMap.has("TOTAL")).toBe(true);

			// Check SPEC overages (from mock data: currentOverages: 10, overageCharges: 10, overageRate: 1)
			const specOverage = resourceToOverageMap.get("SPEC");
			expect(specOverage).toEqual({
				overages: 10,
				overageBilling: 10,
				overageRate: 1,
			});

			// Check VIBE overages (from mock data: currentOverages: 15, overageCharges: 7.50, overageRate: 0.5)
			const vibeOverage = resourceToOverageMap.get("VIBE");
			expect(vibeOverage).toEqual({
				overages: 15,
				overageBilling: 7.5,
				overageRate: 0.5,
			});

			// Check TOTAL overages
			const totalOverage = resourceToOverageMap.get("TOTAL");
			expect(totalOverage).toEqual({
				overages: 10 + 15, // Sum of all overages
				overageBilling: 10 + 7.5, // Sum of all billing
				overageRate: 0, // Total doesn't have a single rate
			});

			expect(result.current.shouldShowOverageTable).toBe(true);
		});

		it("should not show overage table for user without overages", () => {
			const mockApi = createMockApi();
			const wrapper = createWrapper(mockApi);

			const { result } = renderHook(
				() =>
					useOverageSettings({
						usageLimits: mockUsageLimitsData.freeUserLowUsage.data,
					}),
				{ wrapper }
			);

			expect(result.current.resourceToOverageMap.size).toBe(0);
			expect(result.current.shouldShowOverageTable).toBe(false);
		});

		it("should handle empty usage breakdowns", () => {
			const mockApi = createMockApi();
			const wrapper = createWrapper(mockApi);

			const usageLimitsWithEmptyBreakdowns: UsageLimits = {
				...mockUsageLimitsData.freeUserLowUsage.data!,
				usageBreakdowns: [],
			};

			const { result } = renderHook(
				() =>
					useOverageSettings({ usageLimits: usageLimitsWithEmptyBreakdowns }),
				{ wrapper }
			);

			expect(result.current.resourceToOverageMap.size).toBe(0);
			expect(result.current.shouldShowOverageTable).toBe(false);
		});

		it("should only include breakdowns with actual overages in resourceToOverageMap", () => {
			const mockApi = createMockApi();
			const wrapper = createWrapper(mockApi);

			const { result } = renderHook(
				() =>
					useOverageSettings({
						usageLimits: mockUsageLimitsData.paidUserWithOverages.data,
					}),
				{ wrapper }
			);

			// Paid user with overages should have entries in the map
			expect(result.current.resourceToOverageMap.size).toBe(3); // 2 resource types have overages + TOTAL type = 3
			expect(result.current.shouldShowOverageTable).toBe(true);

			// Verify specific overages are included
			expect(result.current.resourceToOverageMap.get("SPEC")).toEqual({
				overages: 10,
				overageBilling: 10,
				overageRate: 1,
			});
		});

		it("should exclude breakdowns with no overages or charges", () => {
			const mockApi = createMockApi();
			const wrapper = createWrapper(mockApi);

			const { result } = renderHook(
				() =>
					useOverageSettings({
						usageLimits: mockUsageLimitsData.powerUser.data,
					}),
				{ wrapper }
			);

			// Power user has no overages, so map should be empty
			expect(result.current.resourceToOverageMap.size).toBe(0);
			expect(result.current.shouldShowOverageTable).toBe(false);
		});

		it("should not show overage table for user with overage rates but no current overages", () => {
			const mockApi = createMockApi();
			const wrapper = createWrapper(mockApi);

			const { result } = renderHook(
				() =>
					useOverageSettings({
						usageLimits: mockUsageLimitsData.userWithOverageRatesNoCharges.data,
					}),
				{ wrapper }
			);

			// User has overage rates configured but no actual overages
			expect(result.current.overageEnabled).toBe(true);
			expect(result.current.resourceToOverageMap.size).toBe(0);
			expect(result.current.shouldShowOverageTable).toBe(false);
		});
	});

	describe("overage toggle functionality", () => {
		it("should successfully enable overages", async () => {
			const mockApi = createMockApi({ data: true, error: null });
			const wrapper = createWrapper(mockApi);

			const { result } = renderHook(
				() =>
					useOverageSettings({
						usageLimits: mockUsageLimitsData.freeUserLowUsage.data,
					}),
				{ wrapper }
			);

			expect(result.current.overageEnabled).toBe(false);

			await act(async () => {
				await result.current.handleOverageToggle(true);
			});

			expect(result.current.overageEnabled).toBe(true);
			expect(result.current.updating).toBe(false);
			expect(mockApi.enableOverages).toHaveBeenCalledWith(true);
		});

		it("should successfully disable overages", async () => {
			const mockApi = createMockApi({ data: true, error: null });
			const wrapper = createWrapper(mockApi);

			const { result } = renderHook(
				() =>
					useOverageSettings({
						usageLimits: mockUsageLimitsData.paidUserWithOverages.data,
					}),
				{ wrapper }
			);

			expect(result.current.overageEnabled).toBe(true);

			await act(async () => {
				await result.current.handleOverageToggle(false);
			});

			expect(result.current.overageEnabled).toBe(false);
			expect(result.current.updating).toBe(false);
			expect(mockApi.enableOverages).toHaveBeenCalledWith(false);
		});

		it("should handle API error response and revert state", async () => {
			const mockApi = createMockApi({
				data: null,
				error: {
					apiError: new Error("Failed to update overage settings"),
					message: "Failed to update overage settings",
					statusCode: 500,
					isLoggedIn: true,
					provider: {
						id: "Google",
						label: "Google",
					},
				},
			});
			const wrapper = createWrapper(mockApi);

			const { result } = renderHook(
				() =>
					useOverageSettings({
						usageLimits: mockUsageLimitsData.freeUserLowUsage.data,
					}),
				{ wrapper }
			);

			await act(async () => {
				await result.current.handleOverageToggle(true);
			});

			expect(result.current.overageEnabled).toBe(false); // State reverted back
			expect(result.current.updating).toBe(false);
		});

		it("should handle API exception and revert state", async () => {
			const mockApi = createMockApi(Promise.reject(new Error("Network error")));
			const wrapper = createWrapper(mockApi);

			const { result } = renderHook(
				() =>
					useOverageSettings({
						usageLimits: mockUsageLimitsData.freeUserLowUsage.data,
					}),
				{ wrapper }
			);

			await act(async () => {
				await result.current.handleOverageToggle(true);
			});

			expect(result.current.overageEnabled).toBe(false); // State reverted back
			expect(result.current.updating).toBe(false);
		});

		it("should handle non-Error exceptions and revert state", async () => {
			const mockApi = createMockApi(Promise.reject("String error"));
			const wrapper = createWrapper(mockApi);

			const { result } = renderHook(
				() =>
					useOverageSettings({
						usageLimits: mockUsageLimitsData.freeUserLowUsage.data,
					}),
				{ wrapper }
			);

			await act(async () => {
				await result.current.handleOverageToggle(true);
			});

			expect(result.current.overageEnabled).toBe(false); // State reverted back
			expect(result.current.updating).toBe(false);
		});

		it("should not make API call when no usage limits provided", async () => {
			const mockApi = createMockApi();
			const wrapper = createWrapper(mockApi);

			const { result } = renderHook(
				() => useOverageSettings({ usageLimits: null }),
				{ wrapper }
			);

			await act(async () => {
				await result.current.handleOverageToggle(true);
			});

			expect(mockApi.enableOverages).not.toHaveBeenCalled();
			expect(result.current.overageEnabled).toBe(false);
		});
	});

	describe("state updates", () => {
		it("should initialize correctly with different usage limits", () => {
			const mockApi = createMockApi();
			const wrapper = createWrapper(mockApi);

			// Test with free user
			const { result: freeResult } = renderHook(
				() =>
					useOverageSettings({
						usageLimits: mockUsageLimitsData.freeUserLowUsage.data,
					}),
				{ wrapper }
			);

			expect(freeResult.current.overageEnabled).toBe(false);
			expect(freeResult.current.resourceToOverageMap.size).toBe(0);

			// Test with paid user with overages
			const { result: paidResult } = renderHook(
				() =>
					useOverageSettings({
						usageLimits: mockUsageLimitsData.paidUserWithOverages.data,
					}),
				{ wrapper }
			);

			expect(paidResult.current.overageEnabled).toBe(true);
			expect(paidResult.current.resourceToOverageMap.size).toBeGreaterThan(0);
		});

		it("should handle null usage limits", () => {
			const mockApi = createMockApi();
			const wrapper = createWrapper(mockApi);

			const { result } = renderHook(
				() => useOverageSettings({ usageLimits: null }),
				{ wrapper }
			);

			expect(result.current.overageEnabled).toBe(false);
			expect(result.current.resourceToOverageMap.size).toBe(0);
			expect(result.current.shouldShowOverageTable).toBe(false);
		});
	});

	describe("generateOverageText", () => {
		it("should return empty string when no overage rates", () => {
			const mockApi = createMockApi();
			const wrapper = createWrapper(mockApi);

			const { result } = renderHook(
				() =>
					useOverageSettings({
						usageLimits: mockUsageLimitsData.freeUserLowUsage.data,
					}),
				{ wrapper }
			);

			expect(result.current.generateOverageText()).toBe("");
		});

		it("should format single overage rate correctly", () => {
			const mockApi = createMockApi();
			const wrapper = createWrapper(mockApi);

			const { result } = renderHook(
				() =>
					useOverageSettings({
						usageLimits: mockUsageLimitsData.singleOverageRate.data,
					}),
				{ wrapper }
			);

			expect(result.current.generateOverageText()).toBe(
				"$0.02 per additional Vibe"
			);
		});

		it("should format two overage rates correctly", () => {
			const mockApi = createMockApi();
			const wrapper = createWrapper(mockApi);

			const { result } = renderHook(
				() =>
					useOverageSettings({
						usageLimits: mockUsageLimitsData.twoOverageRates.data,
					}),
				{ wrapper }
			);

			expect(result.current.generateOverageText()).toBe(
				"$0.01 per additional Vibe and $0.01 per additional Spec"
			);
		});

		it("should format two overage rates correctly with 'and'", () => {
			const mockApi = createMockApi();
			const wrapper = createWrapper(mockApi);

			const { result } = renderHook(
				() =>
					useOverageSettings({
						usageLimits: mockUsageLimitsData.userWithOverageRatesNoCharges.data,
					}),
				{ wrapper }
			);

			const text = result.current.generateOverageText();
			expect(text).toContain("$0.01 per additional Spec");
			expect(text).toContain("$0.75 per additional Vibe");
			expect(text).toContain(" and ");
		});

		it("should handle rates with different decimal places", () => {
			const mockApi = createMockApi();
			const wrapper = createWrapper(mockApi);

			const { result } = renderHook(
				() =>
					useOverageSettings({
						usageLimits: mockUsageLimitsData.paidUserWithOverages.data,
					}),
				{ wrapper }
			);

			const text = result.current.generateOverageText();
			expect(text).toContain("$1.00 per additional Spec");
			expect(text).toContain("$0.50 per additional Vibe");
		});
	});

	describe("display names", () => {
		it("should prefer using display names if available", () => {
			const mockApi = createMockApi();
			const wrapper = createWrapper(mockApi);

			const { result } = renderHook(
				() =>
					useOverageSettings({
						usageLimits: mockUsageLimitsData.withDisplayNames.data,
					}),
				{ wrapper }
			);

			const { generateOverageText, resourceToOverageMap } = result.current;

			expect(resourceToOverageMap.get("FOOBAR" as ResourceType)).toMatchObject({
				displayName: "Singular Foobar",
				displayNamePlural: "Multiple Foobars",
			});
			expect(generateOverageText()).toContain(
				"$1.00 per additional Singular Foobar"
			);
		});
	});
});
