package middleware

import (
	"context"
	"fmt"
	"heruvym/jwt_adapter"
	"net/http"
	"strings"
	"time"

	errors2 "github.com/pkg/errors"
	"github.com/rs/xid"
	"github.com/themakers/hlog"
)

// My MiddleWare with gorilla/mux

type Middleware struct {
	logger hlog.Logger
	//	mongo          dal.LayerMongoDb
	allowedOrigins string
	allowedRoles   map[string]string // key - path, value - roles
}

const sessionKey = "sess"

func NewMiddleware(
	logger hlog.Logger,
	//mongo dal.LayerMongoDb,
	allowedOrigins string,
	allowedRoles map[string]string,
) *Middleware {
	return &Middleware{
		logger: logger,
		//	mongo:          mongo,
		allowedOrigins: allowedOrigins,
		allowedRoles:   allowedRoles,
	}
}

func (mw *Middleware) MiddlewareLogger(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		mw.logger.Emit(DebugHttpRequest{Url: r.URL.String()})
		next.ServeHTTP(w, r)
	})
}

func (mw *Middleware) MiddlewareOriginAccess(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if len(r.Header["Origin"]) > 0 {
			if mw.allowedOrigins != "*" && !strings.Contains(mw.allowedOrigins, r.Header["Origin"][0]) {
				mw.logger.Emit(ErrorOriginAccess{Origin: r.Header["Origin"][0], Url: r.URL.String()})
				return
			}
		}
		next.ServeHTTP(w, r)
	})
}

func recFn(rec interface{}) (int, string) {
	var (
		code    int
		message string
	)

	if err, ok := rec.(error); ok {
		code = http.StatusInternalServerError
		message = err.Error()
	} else {
		code = http.StatusInternalServerError
		message = fmt.Sprintf("%v", rec)
	}

	return code, message
}

func (mw *Middleware) MiddlewareRecovery(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if rec := recover(); rec != nil {
				code, message := recFn(rec)
				w.WriteHeader(code)
				if _, err := fmt.Fprint(w, message); err != nil {
					mw.logger.Emit(ErrorWritingPanicResponse{Err: err})
				}
				mw.logger.Emit(ErrorPanicInHttpHandler{
					Code:      code,
					Message:   message,
					Recovered: rec,
				})
			}
		}()
		next.ServeHTTP(w, r)
	})
}

func (mw *Middleware) MiddlewareJwt(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

		var (
			token, role string
			adapter     *jwt_adapter.JwtAdapter
		)

		switch r.Header["Referer"][0] {
		case "admin.pena.digital":
			role = "admin"
		default:
			role = "user"
		}
		ctx := context.WithValue(r.Context(), jwt_adapter.RoleKey, role)

		tokenCookie, err := r.Cookie(jwt_adapter.DefaultHeaderKey)
		if err != nil {
			// Escape GET requests
			if r.Method == http.MethodGet {
				next.ServeHTTP(w, r.WithContext(ctx))
				return
			}
			if len(r.Header[jwt_adapter.DefaultHeaderKey]) <= 0 {
				if sessCookie, err := r.Cookie(sessionKey); err != nil {
					id := xid.New().String()
					adapter = &jwt_adapter.JwtAdapter{Id: id}
					http.SetCookie(w, &http.Cookie{
						Name:    sessionKey,
						Value:   id,
						Expires: time.Now().Add(time.Hour * 24 * 30),
					})
				} else {
					adapter = &jwt_adapter.JwtAdapter{Id: sessCookie.Value}
				}
			} else {
				token = r.Header[jwt_adapter.DefaultHeaderKey][0]
				token = strings.Replace(token, "Bearer ", "", -1)
			}
		} else {
			token = tokenCookie.Value
		}

		if adapter == nil {
			adapter, err = jwt_adapter.Decode(token)

			if err != nil {
				mw.logger.Emit(ErrorJwtAccess{Err: err})
				w.WriteHeader(http.StatusUnauthorized)
				return
			}
		}

		err = setJwtHeader(adapter, w, mw.logger)
		if err != nil {
			mw.logger.Emit(ErrorJwtAccess{Err: err})
			w.WriteHeader(http.StatusUnauthorized)
			return
		}

		ctx = context.WithValue(ctx, jwt_adapter.DefaultHeaderKey, adapter)
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

func getJwtUserId(r *http.Request) (string, error) {
	if jwtAdapter, ok := r.Context().Value(jwt_adapter.DefaultHeaderKey).(*jwt_adapter.JwtAdapter); ok {
		return jwtAdapter.Id, nil
	}

	return "", errors2.New("no token in context")
}

func (mw *Middleware) MiddlewareRoleAccess(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Если доступ по роли задан
		if allowedRoles, ok := mw.allowedRoles[r.URL.Path]; ok {

			// Если роли не указаны
			if allowedRoles == "" {
				next.ServeHTTP(w, r)
				return
			}
			/*
				id, err := getJwtUserId(r)

				if err != nil {
					mw.logger.Emit(ErrorRoleAccess{Err: err})
					http.Error(w, "internal server error", http.StatusInternalServerError)
					return
				}*/
			/*
				role, err := mw.mongo.GetProfileRole(r.Context(), id)

				if err != nil {
					mw.logger.Emit(ErrorRoleAccess{Err: err})
					http.Error(w, "internal server error", http.StatusInternalServerError)
					return
				}
			*/
			// Если у пользователя не задана роль - блокируем доступ
			/*			if role == "" {
							err = errors.UserHaveNoRole("User have no role")
							mw.logger.Emit(ErrorRoleAccess{err})
							http.Error(w, err.Error(), http.StatusForbidden)
							return
						}

						// Если указан астериск - доступ имеет любая роль
						if !(allowedRoles == "*" || strings.Contains(allowedRoles, role)) {
							err = errors.UserHaveNoRole("User role not allowed")
							mw.logger.Emit(ErrorRoleAccess{err})
							http.Error(w, err.Error(), http.StatusForbidden)
							return
						}*/
		}

		next.ServeHTTP(w, r)
	})
}

// MiddlewareJwtPlug jwt заглушка для отладки кода, удалить в релизе
/*
func (mw *Middleware) MiddlewareJwtPlug(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		adapter := jwt_adapter.JwtAdapter{ID: "604b79aced1d431b9e911f56"}
		adapter.Init()
		adapter.SetUserID("604b79aced1d431b9e911f56")
		ctx := context.WithValue(r.Context(), "JWT", &adapter)

		next.ServeHTTP(w, r.WithContext(ctx))
	})
}
*/
