package loggerdataresp

import (
	"io"
	"net"
	"net/http"
	"strings"

	"github.com/fiskerinc/cloud-services/pkg/common/staticerrors"
	er "github.com/fiskerinc/cloud-services/pkg/errors"
	e "github.com/fiskerinc/cloud-services/pkg/mongo/error"
	"github.com/fiskerinc/cloud-services/pkg/validator"
	"go.mongodb.org/mongo-driver/mongo"

	"github.com/fiskerinc/cloud-services/pkg/logger"
	"github.com/fiskerinc/cloud-services/pkg/utils"
	"github.com/go-pg/pg/v10"
	"github.com/pkg/errors"
)

// Maybe if i move this out we will see all our problems solved

var MongoUpdateErrorCheck = ErrorCheck{Err: e.ErrUnableToUpdate, Status: http.StatusNotFound}
var MongoDeleteErrorCheck = ErrorCheck{Err: e.ErrUnableToDelete, Status: http.StatusNotFound}
var PostgresNoRowsErrorCheck = ErrorCheck{Err: pg.ErrNoRows, Status: http.StatusNotFound}
var EofErrorCheck = ErrorCheck{Err: io.EOF, Status: http.StatusNotFound}
var CustomErrorCheck = ErrorCheck{}

type ErrorCheck struct {
	Err    error
	Status int
}

type respErr func(w http.ResponseWriter, status int, message string)

// log an error message without populating an HTTP response
func BadDataError(err error, options ...ErrorCheck) bool {
	return badDataErrorResp(nil, err, 0, nil, options)
}

// log an error message and populate an HTTP response
func BadDataErrorResp(w http.ResponseWriter, err error, defaultStatus int, options ...ErrorCheck) bool {
	return badDataErrorResp(w, err, defaultStatus, utils.RespError, options)
}

// log an error message and populate an XML HTTP response
func BadDataErrorXMLResp(w http.ResponseWriter, err error, defaultStatus int, options ...ErrorCheck) bool {
	return badDataErrorResp(w, err, defaultStatus, utils.RespXMLError, options)
}

// log an error message and populate an JSON RPC response
func BadDataErrorJSONRPCResp(w http.ResponseWriter, errRPC error, err error) bool {
	if err == nil {
		return false
	}

	utils.RespJSONRPCError(w, errRPC, err.Error())

	if errRPC == utils.ErrJSONRPCServerError || errRPC == utils.ErrJSONRPCInternalError {
		logger.Error().Err(err).Send()
	} else {
		logger.Warn().Err(err).Send()
	}

	return true
}

func badDataErrorResp(w http.ResponseWriter, err error, defaultStatus int, errFunc respErr, errorChecks []ErrorCheck) bool {
	if err == nil {
		return false
	}

	cause := errors.Cause(err)

	if ok, msg := validator.GetValidationErrorMsg(cause); ok {
		logError(err, msg, http.StatusBadRequest, errFunc, w)
		return true
	}

	if customErr, ok := cause.(*er.CustomError); ok {
		logError(customErr.Err(), customErr.Message, customErr.Status, errFunc, w)
		return true
	}

	for _, errorCheck := range errorChecks {
		if errors.Is(errorCheck.Err, cause) {
			logError(errorCheck.Err, errorCheck.Err.Error(), errorCheck.Status, errFunc, w)
			return true
		}
	}

	if handleMongoErrors(err, errFunc, w) {
		return true
	}

	if handlePgErrors(err, errFunc, w) {
		return true
	}

	if handleRedisErrors(err, errFunc, w) {
		return true
	}

	if handleSAPErrors(err, errFunc, w) {
		return true
	}

	logError(err, err.Error(), defaultStatus, errFunc, w)

	return true
}

func handleMongoErrors(err error, errFunc respErr, w http.ResponseWriter) bool {
	if mongo.IsDuplicateKeyError(err) {
		logError(err, err.Error(), http.StatusBadRequest, errFunc, w)
		return true
	}

	if mongo.IsTimeout(err) {
		logError(err, err.Error(), http.StatusServiceUnavailable, errFunc, w)
		return true
	}

	if mongo.IsNetworkError(err) {
		logError(err, err.Error(), http.StatusServiceUnavailable, errFunc, w)
		return true
	}

	return false
}

func handlePgErrors(err error, errFunc respErr, w http.ResponseWriter) bool {
	if pgErr, ok := err.(pg.Error); ok {
		switch pgErr.Field('V') {
		case "FATAL", "PANIC":
			logError(pgErr, pgErr.Error(), http.StatusServiceUnavailable, errFunc, w)
			return true
		}
	}

	return false
}

func handleRedisErrors(err error, errFunc respErr, w http.ResponseWriter) bool {
	if opError, ok := err.(*net.OpError); ok {
		logError(opError, opError.Error(), http.StatusServiceUnavailable, errFunc, w)
		return true
	}

	if dnsError, ok := err.(*net.DNSError); ok {
		logError(dnsError, dnsError.Error(), http.StatusServiceUnavailable, errFunc, w)
		return true
	}

	return false
}

func handleSAPErrors(err error, errFunc respErr, w http.ResponseWriter) bool {
	if strings.HasPrefix(err.Error(), staticerrors.ErrorSAPFailedCall) {
		logError(err, err.Error(), http.StatusServiceUnavailable, errFunc, w)
		return true
	}

	return false
}

func logError(err error, errMessage string, status int, errFunc respErr, w http.ResponseWriter) {
	switch status {
	case http.StatusServiceUnavailable, http.StatusInternalServerError:
		logger.Error().Err(err).Send()
	case http.StatusNotFound, http.StatusBadRequest, http.StatusForbidden:
		logger.Warn().Err(err).Send()
	default:
		logger.Warn().Err(err).Send()
	}

	if errFunc != nil && w != nil {
		errFunc(w, status, errMessage)
	}
}
