/**
 * ApplicationError 错误类层次 + i18n key 生成 单元测试.
 */
import { describe, it, expect } from "vitest";
import {
  ApplicationError,
  ValidationError,
  UnauthorizedError,
  ForbiddenResourceError,
  IdentityMismatchError,
  NotFoundError,
  ConflictError,
  BusinessError,
  BadGatewayError,
  GatewayTimeoutError,
  ServiceUnavailableError,
  InternalError,
} from "./application-error.js";

describe("ApplicationError", () => {
  describe("ValidationError", () => {
    it("should have 400 status and BFF_STUDENT_VALIDATION_ERROR code", () => {
      const err = new ValidationError("Invalid input");
      expect(err.statusCode).toBe(400);
      expect(err.code).toBe("BFF_STUDENT_VALIDATION_ERROR");
      expect(err.type).toBe("validation");
      expect(err.message).toBe("Invalid input");
    });

    it("should accept details", () => {
      const err = new ValidationError("Invalid input", { field: "email" });
      expect(err.details).toEqual({ field: "email" });
    });
  });

  describe("UnauthorizedError", () => {
    it("should have 401 status and BFF_STUDENT_UNAUTHORIZED code", () => {
      const err = new UnauthorizedError();
      expect(err.statusCode).toBe(401);
      expect(err.code).toBe("BFF_STUDENT_UNAUTHORIZED");
      expect(err.type).toBe("unauthorized");
    });

    it("should use default message", () => {
      const err = new UnauthorizedError();
      expect(err.message).toBe("Missing or invalid x-user-id");
    });

    it("should accept custom message", () => {
      const err = new UnauthorizedError("Token expired");
      expect(err.message).toBe("Token expired");
    });
  });

  describe("ForbiddenResourceError (场景 A)", () => {
    it("should have 403 status and BFF_STUDENT_FORBIDDEN_RESOURCE code", () => {
      const err = new ForbiddenResourceError("Not your data");
      expect(err.statusCode).toBe(403);
      expect(err.code).toBe("BFF_STUDENT_FORBIDDEN_RESOURCE");
      expect(err.type).toBe("permission_denied");
    });
  });

  describe("IdentityMismatchError (场景 B)", () => {
    it("should have 403 status and BFF_STUDENT_IDENTITY_MISMATCH code", () => {
      const err = new IdentityMismatchError("Identity mismatch");
      expect(err.statusCode).toBe(403);
      expect(err.code).toBe("BFF_STUDENT_IDENTITY_MISMATCH");
      expect(err.type).toBe("permission_denied");
    });
  });

  describe("NotFoundError", () => {
    it("should have 404 status and BFF_STUDENT_NOT_FOUND code", () => {
      const err = new NotFoundError("Homework", "hw-123");
      expect(err.statusCode).toBe(404);
      expect(err.code).toBe("BFF_STUDENT_NOT_FOUND");
      expect(err.message).toBe("Homework not found: hw-123");
      expect(err.details).toEqual({ resource: "Homework", id: "hw-123" });
    });
  });

  describe("ConflictError", () => {
    it("should have 409 status and BFF_STUDENT_CONFLICT code", () => {
      const err = new ConflictError("Already submitted");
      expect(err.statusCode).toBe(409);
      expect(err.code).toBe("BFF_STUDENT_CONFLICT");
    });
  });

  describe("BusinessError", () => {
    it("should have 422 status and BFF_STUDENT_BUSINESS_ERROR code", () => {
      const err = new BusinessError("Business rule violated");
      expect(err.statusCode).toBe(422);
      expect(err.code).toBe("BFF_STUDENT_BUSINESS_ERROR");
    });
  });

  describe("BadGatewayError", () => {
    it("should have 502 status and BFF_STUDENT_BAD_GATEWAY code", () => {
      const err = new BadGatewayError("Downstream failed");
      expect(err.statusCode).toBe(502);
      expect(err.code).toBe("BFF_STUDENT_BAD_GATEWAY");
    });
  });

  describe("GatewayTimeoutError", () => {
    it("should have 504 status and BFF_STUDENT_GATEWAY_TIMEOUT code", () => {
      const err = new GatewayTimeoutError("Downstream timeout");
      expect(err.statusCode).toBe(504);
      expect(err.code).toBe("BFF_STUDENT_GATEWAY_TIMEOUT");
    });
  });

  describe("ServiceUnavailableError", () => {
    it("should have 503 status and BFF_STUDENT_SERVICE_UNAVAILABLE code", () => {
      const err = new ServiceUnavailableError("Circuit breaker open");
      expect(err.statusCode).toBe(503);
      expect(err.code).toBe("BFF_STUDENT_SERVICE_UNAVAILABLE");
    });
  });

  describe("InternalError", () => {
    it("should have 500 status and BFF_STUDENT_INTERNAL_ERROR code", () => {
      const err = new InternalError("Unexpected error");
      expect(err.statusCode).toBe(500);
      expect(err.code).toBe("BFF_STUDENT_INTERNAL_ERROR");
    });
  });

  describe("toJSON() serialization", () => {
    it("should serialize to ActionState error envelope", () => {
      const err = new ValidationError("Invalid input", { field: "name" });
      err.traceId = "trace-abc";
      const json = err.toJSON();
      const error = (
        json as {
          error: {
            code: string;
            message: string;
            i18nKey?: string;
            details?: unknown;
            traceId?: string;
          };
        }
      ).error;
      expect(json.success).toBe(false);
      expect(json.error).toBeDefined();
      expect(error.code).toBe("BFF_STUDENT_VALIDATION_ERROR");
      expect(error.message).toBe("Invalid input");
      expect(error.i18nKey).toBe("error.bffStudent.validation_error");
      expect(error.details).toEqual({ field: "name" });
      expect(error.traceId).toBe("trace-abc");
    });

    it("should generate correct i18n key for each error code", () => {
      const cases = [
        {
          error: new ValidationError("test"),
          expectedKey: "error.bffStudent.validation_error",
        },
        {
          error: new UnauthorizedError(),
          expectedKey: "error.bffStudent.unauthorized",
        },
        {
          error: new ForbiddenResourceError("test"),
          expectedKey: "error.bffStudent.forbidden_resource",
        },
        {
          error: new IdentityMismatchError("test"),
          expectedKey: "error.bffStudent.identity_mismatch",
        },
        {
          error: new NotFoundError("X", "1"),
          expectedKey: "error.bffStudent.not_found",
        },
        {
          error: new ConflictError("test"),
          expectedKey: "error.bffStudent.conflict",
        },
        {
          error: new BusinessError("test"),
          expectedKey: "error.bffStudent.business_error",
        },
        {
          error: new BadGatewayError("test"),
          expectedKey: "error.bffStudent.bad_gateway",
        },
        {
          error: new GatewayTimeoutError("test"),
          expectedKey: "error.bffStudent.gateway_timeout",
        },
        {
          error: new ServiceUnavailableError("test"),
          expectedKey: "error.bffStudent.service_unavailable",
        },
        {
          error: new InternalError("test"),
          expectedKey: "error.bffStudent.internal_error",
        },
      ];

      for (const { error, expectedKey } of cases) {
        const json = error.toJSON();
        const errObj = (json as { error: { i18nKey?: string } }).error;
        expect(errObj.i18nKey).toBe(expectedKey);
      }
    });
  });

  describe("Error name", () => {
    it("should set constructor name as error.name", () => {
      expect(new ValidationError("x").name).toBe("ValidationError");
      expect(new UnauthorizedError().name).toBe("UnauthorizedError");
      expect(new ForbiddenResourceError("x").name).toBe(
        "ForbiddenResourceError",
      );
      expect(new IdentityMismatchError("x").name).toBe("IdentityMismatchError");
    });
  });

  describe("instanceof checks", () => {
    it("should be instanceof ApplicationError", () => {
      expect(new ValidationError("x")).toBeInstanceOf(ApplicationError);
      expect(new UnauthorizedError()).toBeInstanceOf(ApplicationError);
      expect(new ForbiddenResourceError("x")).toBeInstanceOf(ApplicationError);
    });

    it("should be instanceof Error", () => {
      expect(new ValidationError("x")).toBeInstanceOf(Error);
      expect(new InternalError("x")).toBeInstanceOf(Error);
    });
  });
});
