import { describe, test, expect } from "bun:test";
import GenericError from "../../src/Errors/GenericError";
import AuthenticationError from "../../src/Errors/AuthenticationError";
import AuthorizationError from "../../src/Errors/AuthorizationError";
import BodyError from "../../src/Errors/BodyError";
import InsufficientPermission from "../../src/Errors/InsufficientPermission";
import SessionError from "../../src/Errors/SessionError";
import SessionTokenError from "../../src/Errors/SessionTokenError";
import UserError from "../../src/Errors/UserError";
import RoleError from "../../src/Errors/RoleError";
import MissingBodyValue from "../../src/Errors/MissingBodyValue";
import ExpiredAccessTokenError from "../../src/Errors/ExpiredAccessTokenError";
import ExpiredRefreshTokenError from "../../src/Errors/ExpiredRefreshTokenError";
import PermissionsVerificationError from "../../src/Errors/PermissionsVerificationError";

// ---------------------------------------------------------------------------
// GenericError
// ---------------------------------------------------------------------------
describe("GenericError", () => {
  test("sets name, message, status, and cause", () => {
    const err = new GenericError({
      name: "TestError",
      message: "Something went wrong",
      cause: "bad input",
      status: 422,
    });
    expect(err).toBeInstanceOf(Error);
    expect(err.name).toBe("TestError");
    expect(err.message).toBe("Something went wrong");
    expect(err.cause).toBe("bad input");
    expect(err.status).toBe(422);
  });

  test("defaults status to 400", () => {
    const err = new GenericError({ name: "X", message: "Y" });
    expect(err.status).toBe(400);
  });

  test("cause is optional", () => {
    const err = new GenericError({ name: "X", message: "Y" });
    expect(err.cause).toBeUndefined();
  });
});

// ---------------------------------------------------------------------------
// AuthenticationError
// ---------------------------------------------------------------------------
describe("AuthenticationError", () => {
  test("sets correct name and message", () => {
    const err = new AuthenticationError("Invalid credentials");
    expect(err).toBeInstanceOf(Error);
    expect(err.name).toBe("AuthenticationError");
    expect(err.message).toBe("Invalid credentials");
  });

  test("accepts optional cause", () => {
    const err = new AuthenticationError("Fail", "token expired");
    expect(err.cause).toBe("token expired");
  });

  test("cause defaults to undefined", () => {
    const err = new AuthenticationError("Fail");
    expect(err.cause).toBeUndefined();
  });
});

// ---------------------------------------------------------------------------
// AuthorizationError
// ---------------------------------------------------------------------------
describe("AuthorizationError", () => {
  test("sets correct name and default status", () => {
    const err = new AuthorizationError("Not authorized");
    expect(err).toBeInstanceOf(Error);
    expect(err.name).toBe("AuthorizationError");
    expect(err.message).toBe("Not authorized");
    expect(err.status).toBe(401);
  });

  test("allows custom status", () => {
    const err = new AuthorizationError("Forbidden", "nope", 403);
    expect(err.status).toBe(403);
    expect(err.cause).toBe("nope");
  });
});

// ---------------------------------------------------------------------------
// BodyError
// ---------------------------------------------------------------------------
describe("BodyError", () => {
  test("sets name and message", () => {
    const err = new BodyError("Body is invalid");
    expect(err.name).toBe("BodyError");
    expect(err.message).toBe("Body is invalid");
  });

  test("accepts optional cause", () => {
    const err = new BodyError("Bad", "missing field");
    expect(err.cause).toBe("missing field");
  });
});

// ---------------------------------------------------------------------------
// InsufficientPermission
// ---------------------------------------------------------------------------
describe("InsufficientPermission", () => {
  test("always has status 403", () => {
    const err = new InsufficientPermission("Nope");
    expect(err.name).toBe("InsufficientPermission");
    expect(err.status).toBe(403);
    expect(err.message).toBe("Nope");
  });

  test("accepts optional cause", () => {
    const err = new InsufficientPermission("Nope", "missing role");
    expect(err.cause).toBe("missing role");
  });
});

// ---------------------------------------------------------------------------
// SessionError
// ---------------------------------------------------------------------------
describe("SessionError", () => {
  test("sets name and message", () => {
    const err = new SessionError("Invalid session");
    expect(err.name).toBe("SessionError");
    expect(err.message).toBe("Invalid session");
  });
});

// ---------------------------------------------------------------------------
// SessionTokenError
// ---------------------------------------------------------------------------
describe("SessionTokenError", () => {
  test("sets name and message", () => {
    const err = new SessionTokenError("Token invalid");
    expect(err.name).toBe("SessionTokenError");
    expect(err.message).toBe("Token invalid");
  });

  test("accepts cause", () => {
    const err = new SessionTokenError("Bad", "expired");
    expect(err.cause).toBe("expired");
  });
});

// ---------------------------------------------------------------------------
// UserError
// ---------------------------------------------------------------------------
describe("UserError", () => {
  test("sets name and message", () => {
    const err = new UserError("User not found");
    expect(err.name).toBe("UserError");
    expect(err.message).toBe("User not found");
  });
});

// ---------------------------------------------------------------------------
// RoleError
// ---------------------------------------------------------------------------
describe("RoleError", () => {
  test("sets name and message", () => {
    const err = new RoleError("Role conflict");
    expect(err.name).toBe("RoleError");
    expect(err.message).toBe("Role conflict");
  });

  test("accepts cause", () => {
    const err = new RoleError("Conflict", "moniker taken");
    expect(err.cause).toBe("moniker taken");
  });
});

// ---------------------------------------------------------------------------
// MissingBodyValue
// ---------------------------------------------------------------------------
describe("MissingBodyValue", () => {
  test("formats message with value name", () => {
    const err = new MissingBodyValue("email");
    expect(err.name).toBe("MissingBodyValue");
    expect(err.message).toBe("Value 'email' is missing from the body.");
    expect(err.cause).toBe(
      "A value that was required by the body of this request is missing.",
    );
  });

  test("works with different value names", () => {
    const err = new MissingBodyValue("password");
    expect(err.message).toContain("password");
  });
});

// ---------------------------------------------------------------------------
// ExpiredAccessTokenError
// ---------------------------------------------------------------------------
describe("ExpiredAccessTokenError", () => {
  test("sets fixed name and message", () => {
    const err = new ExpiredAccessTokenError();
    expect(err.name).toBe("ExpiredAccessTokenError");
    expect(err.message).toBe("The provided access token has expired.");
  });

  test("accepts optional cause", () => {
    const err = new ExpiredAccessTokenError("jwt expired");
    expect(err.cause).toBe("jwt expired");
  });
});

// ---------------------------------------------------------------------------
// ExpiredRefreshTokenError
// ---------------------------------------------------------------------------
describe("ExpiredRefreshTokenError", () => {
  test("sets fixed name and message", () => {
    const err = new ExpiredRefreshTokenError();
    expect(err.name).toBe("ExpiredRefreshTokenError");
    expect(err.message).toBe("The provided refresh token has expired.");
  });

  test("accepts optional cause", () => {
    const err = new ExpiredRefreshTokenError("jwt expired");
    expect(err.cause).toBe("jwt expired");
  });
});

// ---------------------------------------------------------------------------
// PermissionsVerificationError
// ---------------------------------------------------------------------------
describe("PermissionsVerificationError", () => {
  test("sets name and message", () => {
    const err = new PermissionsVerificationError("Cannot verify");
    expect(err.name).toBe("PermissionsVerificationError");
    expect(err.message).toBe("Cannot verify");
  });

  test("accepts cause", () => {
    const err = new PermissionsVerificationError("Fail", "key mismatch");
    expect(err.cause).toBe("key mismatch");
  });
});

// ---------------------------------------------------------------------------
// Cross-cutting: all errors are instanceof Error
// ---------------------------------------------------------------------------
describe("All errors extend Error", () => {
  const errors = [
    new GenericError({ name: "G", message: "g" }),
    new AuthenticationError("a"),
    new AuthorizationError("a"),
    new BodyError("b"),
    new InsufficientPermission("i"),
    new SessionError("s"),
    new SessionTokenError("st"),
    new UserError("u"),
    new RoleError("r"),
    new MissingBodyValue("v"),
    new ExpiredAccessTokenError(),
    new ExpiredRefreshTokenError(),
    new PermissionsVerificationError("p"),
  ];

  test.each(errors.map((e) => [e.constructor.name, e]))(
    "%s is instanceof Error",
    (_name, err) => {
      expect(err).toBeInstanceOf(Error);
    },
  );
});
