package routeutils

import (
	"database/sql"
	"fmt"
	"net"
	"net/http"
	"os/exec"
	"reflect"
	"strings"

	"bitbucket.org/nemt/nemt-portal-api/infra/errors"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/go-sql-driver/mysql"
	"github.com/labstack/echo"
)

// resultWrapper has fields for standard message responses
type resultWrapper struct {
	Error    bool        `json:"error,omitempty"`
	Message  string      `json:"message,omitempty"`
	Redirect bool        `json:"redirect,omitempty"`
	Data     interface{} `json:"data,omitempty"`
}

// ResponseNoContent returns a standard API success with no content response
func ResponseNoContent(c echo.Context, data interface{}) error {
	return c.NoContent(http.StatusNoContent)
}

// ResponseAPIOK returns a standard API success response
func ResponseAPIOK(c echo.Context, data interface{}) error {
	return c.JSON(http.StatusOK, data)
}

// ResponseAPIErrorWithData returns a standard API error with additional data to the response
func ResponseAPIErrorWithData(c echo.Context, status int, message string, redirect bool, data interface{}) error {
	returnValue := resultWrapper{
		Error:    true,
		Message:  message,
		Redirect: redirect,
		Data:     data,
	}

	return c.JSON(status, returnValue)
}

// ResponseAPIError returns a standard API error to the response
func ResponseAPIError(c echo.Context, status int, message string, redirect bool) error {
	returnValue := resultWrapper{
		Error:    true,
		Message:  message,
		Redirect: redirect,
	}

	return c.JSON(status, returnValue)
}

// ResponseAPIAuthError returns a standard API auth error to the response
func ResponseAPIAuthError(c echo.Context, message string, redirect bool) error {
	return ResponseAPIError(c, http.StatusUnauthorized, message, redirect)
}

// ResponseAPIAuthorizationError returns a standard API auth error to the response
func ResponseAPIAuthorizationError(c echo.Context) error {
	return ResponseAPIError(c, http.StatusForbidden, "Forbidden by controller", false)
}

// ResponseAPIServiceError returns a standard API service unavailable error to the response
func ResponseAPIServiceError(c echo.Context, message string) error {
	return ResponseAPIError(c, http.StatusServiceUnavailable, message, false)
}

// ResponseAPIValidationError returns a standard API validation error to the response
func ResponseAPIValidationError(c echo.Context, message string) error {
	return ResponseAPIError(c, http.StatusUnprocessableEntity, message, false)
}

// ResponseAPICustomValidationError returns a standard API validation error with custom data to the response
func ResponseAPICustomValidationError(c echo.Context, message string, data interface{}) error {
	return ResponseAPIErrorWithData(c, http.StatusUnprocessableEntity, message, false, data)
}

// ResponseAPIFieldValidationError returns a standard API field validation error to the response
func ResponseAPIFieldValidationError(c echo.Context, field string, message string) error {
	err := errors.NewValidationError(field, message)
	return HandleAPIError(c, err)
}

// ResponseAPINotFoundError returns a standard API not found error to the response
func ResponseAPINotFoundError(c echo.Context) error {
	return ResponseAPIError(c, http.StatusNotFound, "Not Found", false)
}

//ResponseAPINotEligibleError returns a standard API not eligible to the response
func ResponseAPINotEligibleError(c echo.Context) error {
	return ResponseAPIError(c, http.StatusForbidden, "Member does not have active insurance coverage", false)
}

//ResponseAPINotEligibleWithMessageError returns a standard API not eligible to the response with custom message
func ResponseAPINotEligibleWithMessageError(c echo.Context, message string) error {
	return ResponseAPIError(c, http.StatusForbidden, message, false)
}

//ResponseAPIPasswordResetFailed returns a standard API error when password reset fails
func ResponseAPIPasswordResetFailed(c echo.Context, message string) error {
	return ResponseAPIError(c, http.StatusForbidden, message, false)
}

func ignoreDefaultWrappedErrors(c echo.Context, errorToHandle *errors.WrappedError, handler func(echo.Context, error) error) error {
	err := errorToHandle.GetOriginalError()

	if err != nil {
		switch e := (*err).(type) {
		case *errors.ValidationError:
		case *errors.NotAuthorizedError:
		case *errors.HTTPError:
		case awserr.Error:
		default:
			if e != sql.ErrNoRows {
				c.Logger().Errorf("%s", e.Error())
			}
		}

		return handler(c, *err)
	}

	return nil
}

// HandleAPIError applies the default error handling to the response
func HandleAPIError(c echo.Context, errorToHandle error) (err error) {
	statusCode := http.StatusServiceUnavailable
	errorMessage := "Service Unavailable"

	if errorToHandle != nil {
		logger := c.Logger()
		errorString := errorToHandle.Error()

		switch e := errorToHandle.(type) {
		case *errors.WrappedError:
			return ignoreDefaultWrappedErrors(c, e, HandleAPIError)

		case *errors.ApplicationError:
			logger.Errorf("Application Error: %s - %v", e.Path, e.Message)

		case *errors.ValidationError:
			return ResponseAPIValidationError(c, errorString)

		case *errors.NotAuthorizedError:
			statusCode = http.StatusUnauthorized
			errorMessage = errorString

		case *errors.HTTPError:
			statusCode = e.Status
			errorMessage = e.Message

		case *mysql.MySQLError:
			logger.Errorf("MySQL Error: %v - %v", e.Number, e.Message)
			errorMessage = "Service temporarily unavailable"

		case net.Error:
			logger.Errorf("Network/Connection error: %v", e.Error())
			errorMessage = "Service temporarily unavailable"

		case *echo.HTTPError:
			statusCode = e.Code
			errorMessage = fmt.Sprint(e.Message)

		case *exec.ExitError:
			logger.Errorf("Exec Error: %s - %v", e.ProcessState, e.Error())

		default:
			if e == sql.ErrNoRows {
				logger.Debugf("SQL Error: %v", e.Error())
				return ResponseAPINotFoundError(c)
			} else if e == sql.ErrTxDone || strings.HasPrefix(errorString, "sql: ") {
				logger.Errorf("SQL Error: %v", e.Error())
				return ResponseAPIServiceError(c, "Service temporarily unavailable")
			}

			logger.Errorf("%v: %v", reflect.TypeOf(e), e.Error())
		}
	}

	return ResponseAPIError(c, statusCode, errorMessage, false)
}

// HandleHTMLError applies the default error handling to the response
func HandleHTMLError(c echo.Context, errorToHandle error) error {
	statusCode := http.StatusServiceUnavailable
	errorMessage := "Service Unavailable"

	if errorToHandle != nil {
		logger := c.Logger()
		errorString := errorToHandle.Error()

		switch e := errorToHandle.(type) {
		case *errors.WrappedError:
			return ignoreDefaultWrappedErrors(c, e, HandleHTMLError)

		case *errors.ApplicationError:
			logger.Errorf("Application Error: %s - %v", e.Path, e.Message)

		case *errors.ValidationError:
			return ResponseAPIValidationError(c, errorString)

		case *errors.NotAuthorizedError:
			return ResponseAPIError(c, http.StatusUnauthorized, errorString, false)

		case *errors.HTTPError:
			errorMessage = e.Message

		case *mysql.MySQLError:
			logger.Errorf("MySQL Error: %v - %v", e.Number, e.Message)
			errorMessage = "Service temporarily unavailable"

		case net.Error:
			logger.Errorf("Network/Connection error: %v", e.Error())
			errorMessage = "Service temporarily unavailable"

		case *echo.HTTPError:
			logger.Errorf("%v: %v - %v", reflect.TypeOf(e), e.Code, e.Message)
			statusCode = e.Code

		default:
			if e == sql.ErrNoRows {
				logger.Debugf("SQL Error: %v", e.Error())
				statusCode = http.StatusNotFound
				errorMessage = "Not Found"
			} else if e == sql.ErrTxDone || strings.HasPrefix(errorString, "sql: ") {
				logger.Errorf("SQL Error: %v", e.Error())
				errorMessage = "Service temporarily unavailable"
			} else {
				logger.Errorf("%v: %v", reflect.TypeOf(e), e.Error())
			}
		}
	}

	return c.HTML(statusCode, errorMessage)
}
