package com.banesco.common.domain.exception;

import lombok.*;

import java.util.Map;
import java.util.HashMap;

@Getter
@ToString
@Builder
public class HttpStatusCodeException extends BaseApiException {
    private final int statusCode;

    private static final Map<Integer, String> STATUS_CATEGORIES = new HashMap<>();

    static {
        STATUS_CATEGORIES.put(100, "continue");
        STATUS_CATEGORIES.put(101, "switching-protocols");
        STATUS_CATEGORIES.put(102, "processing");
        STATUS_CATEGORIES.put(103, "early-hints");
        STATUS_CATEGORIES.put(200, "ok");
        STATUS_CATEGORIES.put(201, "created");
        STATUS_CATEGORIES.put(202, "accepted");
        STATUS_CATEGORIES.put(203, "non-authoritative-information");
        STATUS_CATEGORIES.put(204, "no-content");
        STATUS_CATEGORIES.put(205, "reset-content");
        STATUS_CATEGORIES.put(206, "partial-content");
        STATUS_CATEGORIES.put(207, "multi-status");
        STATUS_CATEGORIES.put(208, "already-reported");
        STATUS_CATEGORIES.put(226, "im-used");
        STATUS_CATEGORIES.put(300, "multiple-choices");
        STATUS_CATEGORIES.put(301, "moved-permanently");
        STATUS_CATEGORIES.put(302, "found");
        STATUS_CATEGORIES.put(303, "see-other");
        STATUS_CATEGORIES.put(304, "not-modified");
        STATUS_CATEGORIES.put(305, "use-proxy");
        STATUS_CATEGORIES.put(307, "temporary-redirect");
        STATUS_CATEGORIES.put(308, "permanent-redirect");
        STATUS_CATEGORIES.put(400, "bad-request");
        STATUS_CATEGORIES.put(401, "unauthorized");
        STATUS_CATEGORIES.put(402, "payment-required");
        STATUS_CATEGORIES.put(403, "forbidden");
        STATUS_CATEGORIES.put(404, "not-found");
        STATUS_CATEGORIES.put(405, "method-not-allowed");
        STATUS_CATEGORIES.put(406, "not-acceptable");
        STATUS_CATEGORIES.put(407, "proxy-authentication-required");
        STATUS_CATEGORIES.put(408, "request-timeout");
        STATUS_CATEGORIES.put(409, "conflict");
        STATUS_CATEGORIES.put(410, "gone");
        STATUS_CATEGORIES.put(411, "length-required");
        STATUS_CATEGORIES.put(412, "precondition-failed");
        STATUS_CATEGORIES.put(413, "payload-too-large");
        STATUS_CATEGORIES.put(414, "uri-too-long");
        STATUS_CATEGORIES.put(415, "unsupported-media-type");
        STATUS_CATEGORIES.put(416, "range-not-satisfiable");
        STATUS_CATEGORIES.put(417, "expectation-failed");
        STATUS_CATEGORIES.put(418, "im-a-teapot");
        STATUS_CATEGORIES.put(421, "misdirected-request");
        STATUS_CATEGORIES.put(422, "unprocessable-entity");
        STATUS_CATEGORIES.put(423, "locked");
        STATUS_CATEGORIES.put(424, "failed-dependency");
        STATUS_CATEGORIES.put(425, "too-early");
        STATUS_CATEGORIES.put(426, "upgrade-required");
        STATUS_CATEGORIES.put(428, "precondition-required");
        STATUS_CATEGORIES.put(429, "too-many-requests");
        STATUS_CATEGORIES.put(431, "request-header-fields-too-large");
        STATUS_CATEGORIES.put(451, "unavailable-for-legal-reasons");
        STATUS_CATEGORIES.put(500, "internal-server-error");
        STATUS_CATEGORIES.put(501, "not-implemented");
        STATUS_CATEGORIES.put(502, "bad-gateway");
        STATUS_CATEGORIES.put(503, "service-unavailable");
        STATUS_CATEGORIES.put(504, "gateway-timeout");
        STATUS_CATEGORIES.put(505, "http-version-not-supported");
        STATUS_CATEGORIES.put(506, "variant-also-negotiates");
        STATUS_CATEGORIES.put(507, "insufficient-storage");
        STATUS_CATEGORIES.put(508, "loop-detected");
        STATUS_CATEGORIES.put(510, "not-extended");
        STATUS_CATEGORIES.put(511, "network-authentication-required");
    }

    public HttpStatusCodeException(int statusCode, String errorCode, String message, String fieldPath) {
        super(errorCode, message, fieldPath, getHttpStatusCategory(statusCode));
        this.statusCode = statusCode;
    }

    public HttpStatusCodeException(int statusCode, String errorCode, String message) {
        this(statusCode, errorCode, message, null);
    }

    public HttpStatusCodeException(int statusCode, String errorCode) {
        this(statusCode, errorCode, getDefaultMessage(statusCode), null);
    }

    public HttpStatusCodeException(int statusCode) {
        this(statusCode, "HTTP_" + statusCode);
    }

    private static String getHttpStatusCategory(int statusCode) {
        String category = STATUS_CATEGORIES.get(statusCode);

        if (category != null) {
            return category;
        }

        if (statusCode >= 100 && statusCode < 200) return "informational";
        if (statusCode >= 200 && statusCode < 300) return "success";
        if (statusCode >= 300 && statusCode < 400) return "redirection";
        if (statusCode >= 400 && statusCode < 500) return "client-error";
        if (statusCode >= 500 && statusCode < 600) return "server-error";
        return "unknown";
    }

    private static String getDefaultMessage(int statusCode) {
        return switch (statusCode) {
            case 200 -> "OK";
            case 201 -> "Created";
            case 202 -> "Accepted";
            case 204 -> "No Content";
            case 400 -> "Bad Request";
            case 401 -> "Unauthorized";
            case 403 -> "Forbidden";
            case 404 -> "Not Found";
            case 405 -> "Method Not Allowed";
            case 408 -> "Request Timeout";
            case 409 -> "Conflict";
            case 410 -> "Gone";
            case 422 -> "Unprocessable Entity";
            case 429 -> "Too Many Requests";
            case 500 -> "Internal Server Error";
            case 502 -> "Bad Gateway";
            case 503 -> "Service Unavailable";
            case 504 -> "Gateway Timeout";
            default -> "HTTP " + statusCode;
        };
    }

    public static HttpStatusCodeException badRequest(String errorCode) {
        return new HttpStatusCodeException(400, errorCode);
    }

    public static HttpStatusCodeException badRequest(String errorCode, String fieldPath) {
        return new HttpStatusCodeException(400, errorCode, getDefaultMessage(400), fieldPath);
    }

    public static HttpStatusCodeException unauthorized(String errorCode) {
        return new HttpStatusCodeException(401, errorCode);
    }

    public static HttpStatusCodeException unauthorized(String errorCode, String fieldPath) {
        return new HttpStatusCodeException(401, errorCode, getDefaultMessage(401), fieldPath);
    }

    public static HttpStatusCodeException forbidden(String errorCode) {
        return new HttpStatusCodeException(403, errorCode);
    }

    public static HttpStatusCodeException forbidden(String errorCode, String fieldPath) {
        return new HttpStatusCodeException(403, errorCode, getDefaultMessage(403), fieldPath);
    }

    public static HttpStatusCodeException notFound(String errorCode) {
        return new HttpStatusCodeException(404, errorCode);
    }

    public static HttpStatusCodeException notFound(String errorCode, String fieldPath) {
        return new HttpStatusCodeException(404, errorCode, getDefaultMessage(404), fieldPath);
    }

    public static HttpStatusCodeException methodNotAllowed(String errorCode) {
        return new HttpStatusCodeException(405, errorCode);
    }

    public static HttpStatusCodeException methodNotAllowed(String errorCode, String fieldPath) {
        return new HttpStatusCodeException(405, errorCode, getDefaultMessage(405), fieldPath);
    }

    public static HttpStatusCodeException conflict(String errorCode) {
        return new HttpStatusCodeException(409, errorCode);
    }

    public static HttpStatusCodeException conflict(String errorCode, String fieldPath) {
        return new HttpStatusCodeException(409, errorCode, getDefaultMessage(409), fieldPath);
    }

    public static HttpStatusCodeException unprocessableEntity(String errorCode) {
        return new HttpStatusCodeException(422, errorCode);
    }

    public static HttpStatusCodeException unprocessableEntity(String errorCode, String fieldPath) {
        return new HttpStatusCodeException(422, errorCode, getDefaultMessage(422), fieldPath);
    }

    public static HttpStatusCodeException tooManyRequests(String errorCode) {
        return new HttpStatusCodeException(429, errorCode);
    }

    public static HttpStatusCodeException tooManyRequests(String errorCode, String fieldPath) {
        return new HttpStatusCodeException(429, errorCode, getDefaultMessage(429), fieldPath);
    }

    public static HttpStatusCodeException internalServer(String errorCode) {
        return new HttpStatusCodeException(500, errorCode);
    }

    public static HttpStatusCodeException internalServer(String errorCode, String fieldPath) {
        return new HttpStatusCodeException(500, errorCode, getDefaultMessage(500), fieldPath);
    }

    public static HttpStatusCodeException badGateway(String errorCode) {
        return new HttpStatusCodeException(502, errorCode);
    }

    public static HttpStatusCodeException badGateway(String errorCode, String fieldPath) {
        return new HttpStatusCodeException(502, errorCode, getDefaultMessage(502), fieldPath);
    }

    public static HttpStatusCodeException serviceUnavailable(String errorCode) {
        return new HttpStatusCodeException(503, errorCode);
    }

    public static HttpStatusCodeException serviceUnavailable(String errorCode, String fieldPath) {
        return new HttpStatusCodeException(503, errorCode, getDefaultMessage(503), fieldPath);
    }

    public static HttpStatusCodeException gatewayTimeout(String errorCode) {
        return new HttpStatusCodeException(504, errorCode);
    }

    public static HttpStatusCodeException gatewayTimeout(String errorCode, String fieldPath) {
        return new HttpStatusCodeException(504, errorCode, getDefaultMessage(504), fieldPath);
    }

    public static HttpStatusCodeException paymentRequired(String errorCode) {
        return new HttpStatusCodeException(402, errorCode);
    }

    public static HttpStatusCodeException paymentRequired(String errorCode, String fieldPath) {
        return new HttpStatusCodeException(402, errorCode, getDefaultMessage(402), fieldPath);
    }

    public static HttpStatusCodeException gone(String errorCode) {
        return new HttpStatusCodeException(410, errorCode);
    }

    public static HttpStatusCodeException gone(String errorCode, String fieldPath) {
        return new HttpStatusCodeException(410, errorCode, getDefaultMessage(410), fieldPath);
    }

    public static HttpStatusCodeException preconditionFailed(String errorCode) {
        return new HttpStatusCodeException(412, errorCode);
    }

    public static HttpStatusCodeException preconditionFailed(String errorCode, String fieldPath) {
        return new HttpStatusCodeException(412, errorCode, getDefaultMessage(412), fieldPath);
    }

    public static HttpStatusCodeException unsupportedMediaType(String errorCode) {
        return new HttpStatusCodeException(415, errorCode);
    }

    public static HttpStatusCodeException unsupportedMediaType(String errorCode, String fieldPath) {
        return new HttpStatusCodeException(415, errorCode, getDefaultMessage(415), fieldPath);
    }

    public static HttpStatusCodeException notImplemented(String errorCode) {
        return new HttpStatusCodeException(501, errorCode);
    }

    public static HttpStatusCodeException notImplemented(String errorCode, String fieldPath) {
        return new HttpStatusCodeException(501, errorCode, getDefaultMessage(501), fieldPath);
    }

    public static HttpStatusCodeException ok(String errorCode) {
        return new HttpStatusCodeException(200, errorCode);
    }

    public static HttpStatusCodeException ok(String errorCode, String fieldPath) {
        return new HttpStatusCodeException(200, errorCode, getDefaultMessage(200), fieldPath);
    }

    public static HttpStatusCodeException created(String errorCode) {
        return new HttpStatusCodeException(201, errorCode);
    }

    public static HttpStatusCodeException created(String errorCode, String fieldPath) {
        return new HttpStatusCodeException(201, errorCode, getDefaultMessage(201), fieldPath);
    }

    public static HttpStatusCodeException accepted(String errorCode) {
        return new HttpStatusCodeException(202, errorCode);
    }

    public static HttpStatusCodeException accepted(String errorCode, String fieldPath) {
        return new HttpStatusCodeException(202, errorCode, getDefaultMessage(202), fieldPath);
    }

    public static HttpStatusCodeException noContent(String errorCode) {
        return new HttpStatusCodeException(204, errorCode);
    }

    public static HttpStatusCodeException noContent(String errorCode, String fieldPath) {
        return new HttpStatusCodeException(204, errorCode, getDefaultMessage(204), fieldPath);
    }

    public static HttpStatusCodeException requestTimeout(String errorCode) {
        return new HttpStatusCodeException(408, errorCode);
    }

    public static HttpStatusCodeException requestTimeout(String errorCode, String fieldPath) {
        return new HttpStatusCodeException(408, errorCode, getDefaultMessage(408), fieldPath);
    }

    public static HttpStatusCodeException fromStatusCode(int statusCode, String errorCode) {
        return new HttpStatusCodeException(statusCode, errorCode);
    }

    public static HttpStatusCodeException fromStatusCode(int statusCode, String errorCode, String fieldPath) {
        return new HttpStatusCodeException(statusCode, errorCode, getDefaultMessage(statusCode), fieldPath);
    }
}