import { describe, expect, it } from "vitest";
import { z } from "zod";

import { ApiError, ErrorCode } from "@/data/services/api/api_result";

import { AppException } from "../app-exception";
import { ExceptionCode } from "../exception-code";
import { ExceptionHandler } from "../exception-handler";

describe("ExceptionHandler", () => {
  it("maps API status errors to stable user messages", () => {
    expect(
      ExceptionHandler.handle(
        new ApiError("HTTP_UNAUTHORIZED", "raw backend message", 401),
      ),
    ).toEqual({
      code: ExceptionCode.unauthorized,
      message: "Session expired, please sign in again.",
    });

    expect(
      ExceptionHandler.handle(
        new ApiError("HTTP_SERVER_ERROR", "internal", 500),
      ),
    ).toEqual({
      code: ExceptionCode.server,
      message: "Server error, please try again later.",
    });
  });

  it("maps known API codes without status", () => {
    expect(
      ExceptionHandler.handle(
        new ApiError(ErrorCode.timeoutError, "request timeout"),
      ),
    ).toEqual({
      code: ExceptionCode.timeout,
      message: "request timeout",
    });
  });

  it("maps network and cancellation errors", () => {
    expect(ExceptionHandler.handle(new TypeError("Failed to fetch"))).toEqual({
      code: ExceptionCode.network,
      message: "Network connection failed.",
    });

    expect(ExceptionHandler.handle(new DOMException("Aborted", "AbortError"))).toEqual({
      code: ExceptionCode.cancelled,
      message: "Request cancelled.",
    });
  });

  it("maps schema and syntax errors to validation errors", () => {
    const parseResult = z.object({ name: z.string() }).safeParse({ name: null });
    if (parseResult.success) throw new Error("Expected parse failure");

    expect(ExceptionHandler.handle(parseResult.error)).toEqual({
      code: ExceptionCode.validation,
      message: "Data format changed unexpectedly. Please try again.",
    });

    expect(ExceptionHandler.handle(new SyntaxError("Unexpected token"))).toEqual({
      code: ExceptionCode.validation,
      message: "Data format changed unexpectedly. Please try again.",
    });
  });

  it("maps storage and payment errors", () => {
    expect(ExceptionHandler.handle(new Error("localStorage unavailable"))).toEqual({
      code: ExceptionCode.storage,
      message: "Local data could not be saved. Please try again.",
    });

    expect(ExceptionHandler.handle(new Error("Stripe payment failed"))).toEqual({
      code: ExceptionCode.payment,
      message: "Payment could not be completed. Please try again.",
    });
  });

  it("maps browser capability and permission errors", () => {
    expect(ExceptionHandler.handle(new Error("MediaRecorder not supported"))).toEqual({
      code: ExceptionCode.unsupported,
      message: "This feature is not supported in this browser.",
    });

    expect(
      ExceptionHandler.handle(
        new DOMException("Microphone permission denied", "NotAllowedError"),
      ),
    ).toEqual({
      code: ExceptionCode.permissionDenied,
      message: "Permission denied.",
    });
  });

  it("preserves an existing AppException result", () => {
    const error = new AppException({
      code: ExceptionCode.notFound,
      message: "Custom not found",
    });

    expect(ExceptionHandler.handle(error)).toEqual({
      code: ExceptionCode.notFound,
      message: "Custom not found",
    });
  });

  it("falls back to a simple message", () => {
    expect(ExceptionHandler.handle("plain failure")).toEqual({
      code: ExceptionCode.unknown,
      message: "plain failure",
    });

    expect(ExceptionHandler.handle({ message: "card declined" })).toEqual({
      code: ExceptionCode.unknown,
      message: "card declined",
    });

    expect(ExceptionHandler.handle({ nope: true })).toEqual({
      code: ExceptionCode.unknown,
      message: "Something went wrong. Please try again.",
    });
  });

  it("uses call-site fallback text only for unrecognized errors", () => {
    expect(ExceptionHandler.message({ nope: true }, "Domain fallback")).toBe(
      "Domain fallback",
    );
    expect(ExceptionHandler.message(new Error("specific"), "Domain fallback")).toBe(
      "specific",
    );
  });
});
