package util

import (
	"bytes"
	"encoding/json"
	"io"
	"net/http"

	"github.com/sorintlab/errors"
)

func HTTPResponse(w http.ResponseWriter, code int, res interface{}) error {
	w.Header().Set("Content-Type", "application/json")

	if res != nil {
		resj, err := json.Marshal(res)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			return errors.WithStack(err)
		}
		w.WriteHeader(code)
		_, err = w.Write(resj)
		return errors.WithStack(err)
	}

	w.WriteHeader(code)
	return nil
}

type errorResponse struct {
	Code    string `json:"code"`
	Details any    `json:"details"`
}

func ErrorResponseFromError(err error) []*errorResponse {
	if err == nil {
		return nil
	}

	res := []*errorResponse{}

	var aerr *APIError
	if errors.As(err, &aerr) {
		for _, detailedError := range aerr.DetailedErrors {
			res = append(res, &errorResponse{
				Code:    string(detailedError.Code),
				Details: detailedError.Details,
			})
		}
	}

	// on generic error return an empty array
	return res
}

func HTTPError(w http.ResponseWriter, err error) bool {
	if err == nil {
		return false
	}

	w.Header().Set("Content-Type", "application/json")

	response := ErrorResponseFromError(err)
	resj, merr := json.Marshal(response)
	if merr != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return true
	}

	code := http.StatusInternalServerError

	var aerr *APIError
	if errors.As(err, &aerr) {
		switch aerr.Kind {
		case ErrBadRequest:
			code = http.StatusBadRequest
		case ErrNotExist:
			code = http.StatusNotFound
		case ErrForbidden:
			code = http.StatusForbidden
		case ErrUnauthorized:
			code = http.StatusUnauthorized
		case ErrInternal:
			code = http.StatusInternalServerError
		}
	}

	w.WriteHeader(code)
	_, _ = w.Write(resj)

	return true
}

func ErrFromRemote(resp *http.Response) error {
	if resp == nil {
		return nil
	}
	if resp.StatusCode/100 == 2 {
		return nil
	}

	errorsRes := []*errorResponse{}

	defer resp.Body.Close()

	data, err := io.ReadAll(resp.Body)
	if err != nil {
		return errors.WithStack(err)
	}

	// Re-populate error response body so it can be parsed by the caller if needed
	resp.Body = io.NopCloser(bytes.NewBuffer(data))

	if err := json.Unmarshal(data, &errorsRes); err != nil {
		return errors.Errorf("unknown api error (status: %d)", resp.StatusCode)
	}

	kind := ErrInternal
	switch resp.StatusCode {
	case http.StatusBadRequest:
		kind = ErrBadRequest
	case http.StatusNotFound:
		kind = ErrNotExist
	case http.StatusForbidden:
		kind = ErrForbidden
	case http.StatusUnauthorized:
		kind = ErrUnauthorized
	case http.StatusInternalServerError:
		kind = ErrInternal
	}

	remoteErrorOptions := []RemoteErrorOption{}
	for _, errorRes := range errorsRes {
		remoteErrorOptions = append(remoteErrorOptions,
			WithRemoteErrorDetailedError(&RemoteDetailedError{Code: ErrorCode(errorRes.Code), Details: errorRes.Details}))
	}

	return NewRemoteError(kind, remoteErrorOptions...)
}
