import { beforeEach, describe, expect, it, vi } from "vitest";

const { mockError, mockRedirect } = vi.hoisted(() => ({
  mockError: vi.fn(),
  mockRedirect: vi.fn(),
}));

vi.mock("@sveltejs/kit", () => ({
  error: mockError,
  redirect: mockRedirect,
}));

import {
  ApiError,
  handleApiError,
  isInvalidSignatureError,
  isNetworkError,
  isUnauthorizedError,
  isForbiddenError,
  isNotFoundError,
} from "./errorHandler";

describe("errorHandler", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.spyOn(console, "error").mockImplementation(() => {});
    vi.spyOn(console, "warn").mockImplementation(() => {});
  });

  // ── ApiError ──────────────────────────────────────────────────────────

  it("ApiError stores statusCode, message, and details", () => {
    const err = new ApiError(422, "Validation failed", { field: "name" });

    expect(err).toBeInstanceOf(Error);
    expect(err.name).toBe("ApiError");
    expect(err.statusCode).toBe(422);
    expect(err.message).toBe("Validation failed");
    expect(err.details).toEqual({ field: "name" });
  });

  // ── isInvalidSignatureError ───────────────────────────────────────────

  it("detects 'invalid signature' in response data message", () => {
    const err = {
      response: { data: { message: "Token has invalid signature" } },
    };
    expect(isInvalidSignatureError(err)).toBe(true);
  });

  it("detects 'jwt malformed' in response data error field", () => {
    const err = {
      response: { data: { error: "jwt malformed" } },
    };
    expect(isInvalidSignatureError(err)).toBe(true);
  });

  it("detects 'invalid token' in Error message", () => {
    const err = new Error("Invalid token received");
    expect(isInvalidSignatureError(err)).toBe(true);
  });

  it("returns false for unrelated errors", () => {
    expect(isInvalidSignatureError(new Error("network down"))).toBe(false);
    expect(isInvalidSignatureError(null)).toBe(false);
    expect(isInvalidSignatureError("string error")).toBe(false);
  });

  // ── handleApiError ────────────────────────────────────────────────────

  it("redirects to /logout on invalid signature error", () => {
    mockRedirect.mockImplementation(() => {
      throw new Error("REDIRECT");
    });

    const err = {
      response: { data: { message: "invalid signature" } },
    };

    expect(() => handleApiError(err)).toThrow("REDIRECT");
    expect(mockRedirect).toHaveBeenCalledWith(303, "/logout");
  });

  it("throws SvelteKit error for ApiError", () => {
    mockError.mockImplementation(() => {
      throw new Error("HTTP_ERROR");
    });

    const err = new ApiError(404, "Not found", "extra");

    expect(() => handleApiError(err)).toThrow("HTTP_ERROR");
    expect(mockError).toHaveBeenCalledWith(404, {
      message: "Not found",
      details: "extra",
    });
  });

  it("throws 500 error for generic Error", () => {
    mockError.mockImplementation(() => {
      throw new Error("HTTP_ERROR");
    });

    const err = new Error("Something broke");

    expect(() => handleApiError(err)).toThrow("HTTP_ERROR");
    expect(mockError).toHaveBeenCalledWith(500, {
      message: "Something broke",
      details: expect.any(String),
    });
  });

  it("throws 500 error for non-Error values", () => {
    mockError.mockImplementation(() => {
      throw new Error("HTTP_ERROR");
    });

    expect(() => handleApiError("string error")).toThrow("HTTP_ERROR");
    expect(mockError).toHaveBeenCalledWith(500, {
      message: "An unexpected error occurred",
      details: "string error",
    });
  });

  // ── isNetworkError ────────────────────────────────────────────────────

  it("identifies Network errors", () => {
    expect(isNetworkError(new Error("Network request failed"))).toBe(true);
    expect(isNetworkError(new Error("fetch failed"))).toBe(true);
    expect(isNetworkError(new Error("ECONNREFUSED"))).toBe(true);
  });

  it("returns false for non-network errors", () => {
    expect(isNetworkError(new Error("validation error"))).toBe(false);
    expect(isNetworkError("not an error")).toBe(false);
  });

  // ── status code helpers ───────────────────────────────────────────────

  it("isUnauthorizedError returns true for 401 ApiError", () => {
    expect(isUnauthorizedError(new ApiError(401, "Unauthorized"))).toBe(true);
    expect(isUnauthorizedError(new ApiError(403, "Forbidden"))).toBe(false);
    expect(isUnauthorizedError(new Error("nope"))).toBe(false);
  });

  it("isForbiddenError returns true for 403 ApiError", () => {
    expect(isForbiddenError(new ApiError(403, "Forbidden"))).toBe(true);
    expect(isForbiddenError(new ApiError(401, "Unauthorized"))).toBe(false);
  });

  it("isNotFoundError returns true for 404 ApiError", () => {
    expect(isNotFoundError(new ApiError(404, "Not Found"))).toBe(true);
    expect(isNotFoundError(new ApiError(500, "Server Error"))).toBe(false);
  });
});
