package middleware

import (
	"context"
	"encoding/json"
	"errors"
	"github.com/Pena-Co-Ltd/amocrm_templategen_back/amo"
	"github.com/Pena-Co-Ltd/amocrm_templategen_back/dal"
	"github.com/dgrijalva/jwt-go"
	"go.uber.org/zap"
	"net/http"
	"strconv"
	"strings"
)

var (
	JwtSecret = []byte("my-secret-123")
)

type Middleware struct {
	dal          *dal.MongoDAL
	amo          *amo.ClientApp
	logger       *zap.Logger
	AmoAccessMap map[string]string // key - endpoint; value - access (visibility, creation, delete)
}

type UserClaims struct {
	UserID      string
	FullName    string
	Email       string
	IsActivated bool
	jwt.StandardClaims
}

func InitMiddleware(dal *dal.MongoDAL, amo *amo.ClientApp, logger *zap.Logger,
	amoAccessMap map[string]string) *Middleware {
	return &Middleware{dal: dal, amo: amo, logger: logger, AmoAccessMap: amoAccessMap}
}

func (mw *Middleware) MiddlewareCors(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method == "OPTIONS" {
			w.WriteHeader(http.StatusOK)
		} else {
			next.ServeHTTP(w, r)
		}
	})
}

func (mw *Middleware) Logger(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if strings.Contains(r.URL.String(), "/assets/") {
			next.ServeHTTP(w, r)
			return
		}

		userId := ""
		user := getJwtUser(r)
		if user != nil {
			userId = user.UserID
		}
		mw.logger.Info("HttpRequest",
			zap.String("URL", r.URL.String()),
			zap.String("UserID", userId),
		)
		next.ServeHTTP(w, r)
	})
}

func (mw *Middleware) MiddlewareJwt(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if strings.Contains(r.URL.String(), "/assets/") {
			next.ServeHTTP(w, r)
			return
		}

		authMap := map[string]interface{}{
			//"/amo": nil,
		}

		required := false
		_, ok := authMap[r.URL.String()]
		if ok {
			required = true
		}

		// Check token in headers
		tokenHeader := r.Header.Get("Authorization")
		if tokenHeader == "" && required {
			w.WriteHeader(http.StatusUnauthorized)
			return
		}

		tokenHeader = strings.Replace(tokenHeader, "Bearer ", "", -1)

		if tokenHeader != "" {
			token, err := jwt.ParseWithClaims(tokenHeader, &UserClaims{}, func(tk *jwt.Token) (interface{}, error) {
				return JwtSecret, nil
			})

			if err != nil {
				mw.logger.Error("ErrJwtAccess", zap.Error(err))
			}

			if claims, ok := token.Claims.(*UserClaims); ok && token.Valid {
				ctx := context.WithValue(r.Context(), "JWT", claims)
				next.ServeHTTP(w, r.WithContext(ctx))
				return
			}
		}

		// Check token in cookies
		c, err := r.Cookie("Authorization")

		if err != nil {
			if !required {
				next.ServeHTTP(w, r)
			} else {
				mw.logger.Error("ErrJwtAccess", zap.Error(err))
				w.WriteHeader(http.StatusUnauthorized)
			}
			return
		}

		tokenCookie := strings.Replace(c.Value, "Bearer ", "", -1)
		token, err := jwt.ParseWithClaims(tokenCookie, &UserClaims{}, func(tk *jwt.Token) (interface{}, error) {
			return JwtSecret, nil
		})

		if err != nil {
			mw.logger.Error("ErrJwtAccess", zap.Error(err))
			if !required {
				next.ServeHTTP(w, r)
			} else {
				w.WriteHeader(http.StatusUnauthorized)
			}
			return
		}

		claims, ok := token.Claims.(*UserClaims)
		if !ok || !token.Valid {
			mw.logger.Error("ErrJwtAccess", zap.Error(err))
			if !required {
				next.ServeHTTP(w, r)
			} else {
				w.WriteHeader(http.StatusUnauthorized)
			}
			return
		}

		ctx := context.WithValue(r.Context(), "JWT", claims)
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

func (mw *Middleware) MiddlewareHeaders(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With, X-Auth-Token")
		w.Header().Set("Access-Control-Allow-Credentials", "true")
		w.Header().Set("Access-Control-Expose-Headers", "*")
		w.Header().Set("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE")
		//w.Header().Add()
		next.ServeHTTP(w, r)
	})
}

func (mw *Middleware) MiddlewareAmoJwt(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if !strings.Contains(r.URL.String(), "/amo/") {
			next.ServeHTTP(w, r)
			return
		}

		token, err := mw.amo.DecodeJwt(r)
		if err != nil {
			mw.logger.Error("ErrAmoJwtAccess", zap.Error(err))
			return
		}

		data, err := mw.dal.Amo.GetByAccountID(r.Context(), strconv.FormatInt(token.AccountId, 10))
		if err != nil {
			mw.reportError(w, err, http.StatusInternalServerError)
			return
		}

		if data == nil {
			mw.reportError(w, errors.New("amo account not found"), http.StatusUnauthorized)
			return
		}

		// check access rules
		isAcessGranted := token.IsAdmin

		// Если права не указаны для эндпоинта, то доступно любому юзеру
		rule, ok := mw.AmoAccessMap[r.URL.String()]
		if ok {
			// Если не админ, то проверяем права пользователя
			if rule == "visibility" && !isAcessGranted {
				for _, userId := range data.AccessRules.Visibility {
					if userId == token.UserId {
						isAcessGranted = true
						break
					}
				}
			}

			if rule == "creation" && !isAcessGranted {
				for _, userId := range data.AccessRules.Creation {
					if userId == token.UserId {
						isAcessGranted = true
						break
					}
				}
			}

			if rule == "delete" && !isAcessGranted {
				for _, userId := range data.AccessRules.Delete {
					if userId == token.UserId {
						isAcessGranted = true
						break
					}
				}
			}
		} else {
			isAcessGranted = true
		}

		// Если прав нет, прерываемся
		if !isAcessGranted {
			mw.reportError(w, errors.New("amoUser forbidden"), http.StatusUnauthorized) // mb: http.StatusForbidden
			return
		}

		ctx := context.WithValue(r.Context(), "amoData", data)
		ctx = context.WithValue(ctx, "amoIsAdmin", token.IsAdmin)
		ctx = context.WithValue(ctx, "amoUserID", token.UserId)
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

func (mw *Middleware) MiddlewareAmoPlug(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if !strings.Contains(r.URL.String(), "/amo/") {
			next.ServeHTTP(w, r)
			return
		}

		data, err := mw.dal.Amo.GetByID(r.Context(), "6303013686610a51755e6247")

		if err != nil {
			mw.reportError(w, err, http.StatusInternalServerError)
			return
		}

		if data == nil {
			mw.reportError(w, errors.New("amo account not found"), http.StatusUnauthorized)
			return
		}

		ctx := context.WithValue(r.Context(), "amoData", data)
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

func getJwtUser(r *http.Request) *UserClaims {
	user, ok := r.Context().Value("JWT").(*UserClaims)
	if ok {
		return user
	}

	return nil
}

func (mw *Middleware) reportError(w http.ResponseWriter, err error, status int) error {
	mw.logger.Error("ErrorMiddleware", zap.Error(err))
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.WriteHeader(status)
	return json.NewEncoder(w).Encode(err)
}
