package daemon

import (
	"context"
	"encoding/json"
	"errors"
	"net/http"
	"strings"

	"github.com/openeuler/Conch/internal/apperror"
	"github.com/openeuler/Conch/pkg/ulog"
)

type apiErrorResponse struct {
	Status string        `json:"status"`
	Code   apperror.Code `json:"code"`
	Error  string        `json:"error"`
}

var (
	errRequestInvalidBody = apperror.Define(
		apperror.InvalidArgument,
		"request.invalid_body",
		"invalid request body",
	)
	errRequestInvalidMultipart = apperror.Define(
		apperror.InvalidArgument,
		"request.invalid_multipart",
		"invalid multipart request",
	)
	errRequestBodyTooLarge = apperror.Define(
		apperror.PayloadTooLarge,
		"request.body_too_large",
		"request body is too large",
	)
	errServiceUnavailable = apperror.Define(
		apperror.Unavailable,
		"service.unavailable",
		"service unavailable",
	)
	errRequestDeadlineExceeded = apperror.Define(
		apperror.DeadlineExceeded,
		"request.deadline_exceeded",
		"request deadline exceeded",
	)
)

var internalAPIError = apiErrorResponse{
	Status: "error",
	Code:   "internal",
	Error:  "internal server error",
}

func classifyAPIError(err error) (int, apiErrorResponse) {
	var appErr *apperror.Error
	if errors.As(err, &appErr) {
		code := appErr.Code()
		message := strings.TrimSpace(appErr.PublicMessage())
		kind := appErr.Kind()
		if code.Valid() && message != "" && kind >= apperror.Internal && kind <= apperror.NotImplemented {
			return httpStatusForErrorKind(kind), apiErrorResponse{
				Status: "error",
				Code:   code,
				Error:  message,
			}
		}
		return http.StatusInternalServerError, internalAPIError
	}
	if errors.Is(err, context.DeadlineExceeded) {
		return http.StatusGatewayTimeout, apiErrorResponse{
			Status: "error",
			Code:   errRequestDeadlineExceeded.Code(),
			Error:  errRequestDeadlineExceeded.PublicMessage(),
		}
	}
	return http.StatusInternalServerError, internalAPIError
}

func httpStatusForErrorKind(kind apperror.Kind) int {
	switch kind {
	case apperror.InvalidArgument:
		return http.StatusBadRequest
	case apperror.Unauthenticated:
		return http.StatusUnauthorized
	case apperror.PermissionDenied:
		return http.StatusForbidden
	case apperror.NotFound:
		return http.StatusNotFound
	case apperror.AlreadyExists, apperror.Conflict, apperror.FailedPrecondition:
		return http.StatusConflict
	case apperror.ResourceExhausted:
		return http.StatusTooManyRequests
	case apperror.PayloadTooLarge:
		return http.StatusRequestEntityTooLarge
	case apperror.UpstreamFailure:
		return http.StatusBadGateway
	case apperror.Unavailable:
		return http.StatusServiceUnavailable
	case apperror.DeadlineExceeded:
		return http.StatusGatewayTimeout
	case apperror.NotImplemented:
		return http.StatusNotImplemented
	case apperror.Internal:
		return http.StatusInternalServerError
	default:
		return http.StatusInternalServerError
	}
}

func writeErrorResponse(w http.ResponseWriter, status int, response apiErrorResponse) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(response)
}

func writeAPIError(w http.ResponseWriter, err error, fields ...ulog.Field) {
	status, response := classifyAPIError(err)
	if status >= http.StatusInternalServerError {
		fields = append([]ulog.Field{
			ulog.F("status_code", status),
			ulog.F("error_code", response.Code),
			ulog.F("error", err),
		}, fields...)
		ulog.GetLogger().Error("API request failed", fields...)
	}
	writeErrorResponse(w, status, response)
}

func writeMethodNotAllowed(w http.ResponseWriter) {
	writeErrorResponse(w, http.StatusMethodNotAllowed, apiErrorResponse{
		Status: "error",
		Code:   "request.method_not_allowed",
		Error:  "method not allowed",
	})
}