package handlers

import (
	"encoding/json"
	"errors"
	"fmt"
	"log/slog"
	"strings"

	"github.com/gofiber/fiber/v2"

	"wucher/internal/shared/pkg/apperrorsx"
	"wucher/internal/transport/http/jsonapi"
)

// bodyParseError maps a request-body JSON parse error to a precise app error. It returns
// a non-nil *AppError only for a data-type mismatch (INVALID_DATA_TYPE, pointing at the
// offending field); for any other parse error it returns nil so the caller can fall back
// to its module-specific invalid-json error.
func bodyParseError(err error) *apperrorsx.AppError {
	var typeErr *json.UnmarshalTypeError
	if !errors.As(err, &typeErr) {
		return nil
	}
	appErr := apperrorsx.New(apperrorsx.ErrInvalidDataType).WithCause(err)
	if typeErr.Field != "" {
		appErr.Source = &jsonapi.ErrorSource{Pointer: "/" + strings.ReplaceAll(typeErr.Field, ".", "/")}
		appErr.Message = fmt.Sprintf("field %q expects type %s", typeErr.Field, typeErr.Type.String())
	} else {
		appErr.Message = fmt.Sprintf("expected type %s", typeErr.Type.String())
	}
	return appErr
}

// writeBodyParseError writes a precise INVALID_DATA_TYPE error for a data-type mismatch,
// or falls back to the given module invalid-json def for any other parse error.
func writeBodyParseError(c *fiber.Ctx, err error, fallback apperrorsx.Def) error {
	if appErr := bodyParseError(err); appErr != nil {
		return apperrorsx.Write(c, appErr)
	}
	return apperrorsx.Write(c, apperrorsx.New(fallback).WithCause(err))
}

func writeInternalHandlerError(c *fiber.Ctx, operation string, err error) error {
	return writeInternalHandlerErrorWithStatus(c, fiber.StatusInternalServerError, operation, err)
}

func writeHandlerErrorDef(c *fiber.Ctx, operation string, def apperrorsx.Def, err error) error {
	logHandlerError(c, operation, def.Status, def.Code, def.ErrorCode, err)
	appErr := apperrorsx.New(def)
	if err != nil {
		appErr.Cause = err
	}
	return apperrorsx.Write(c, appErr)
}

func writeHandlerErrorDefWithStatus(c *fiber.Ctx, operation string, status int, def apperrorsx.Def, err error) error {
	logHandlerError(c, operation, status, def.Code, def.ErrorCode, err)
	appErr := apperrorsx.New(def)
	appErr.Status = status
	if err != nil {
		appErr.Cause = err
	}
	return apperrorsx.Write(c, appErr)
}

func writeInternalHandlerErrorWithStatus(c *fiber.Ctx, status int, operation string, err error) error {
	logHandlerError(c, operation, status, apperrorsx.CodeInternalServerError, apperrorsx.ErrInternal.ErrorCode, err)
	appErr := apperrorsx.New(apperrorsx.ErrInternal)
	appErr.Status = status
	return apperrorsx.Write(c, appErr)
}

func safeInternalDetail() string {
	return apperrorsx.ErrInternal.Message
}

func logHandlerError(c *fiber.Ctx, operation string, status int, code, errorCode string, err error) {
	args := []any{
		slog.String("request_id", requestIDFromCtx(c)),
		slog.String("operation", strings.TrimSpace(operation)),
		slog.Int("status", status),
		slog.String("code", strings.TrimSpace(code)),
		slog.String("error_code", strings.TrimSpace(errorCode)),
		slog.String("path", c.Path()),
		slog.Any("cause", err),
	}
	if status >= fiber.StatusInternalServerError {
		slog.ErrorContext(c.UserContext(), "handler error", args...)
		return
	}
	slog.WarnContext(c.UserContext(), "handler error", args...)
}
