package main

import (
	"context"
	"encoding/base64"
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"strings"

	"github.com/FernandoVideira/LK_API_Temp/internal/store"
	"github.com/go-chi/chi/v5"
	"github.com/golang-jwt/jwt/v5"
)

func (api *api) BasicAuthMiddleware() func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			//* read the auth header
			authHeader := r.Header.Get("Authorization")
			if authHeader == "" {
				api.unauthorizedBasicError(w, r, fmt.Errorf("missing Authorization header"))
				return
			}

			//* parse the header -> get the base64
			parts := strings.Split(authHeader, " ")
			if len(parts) != 2 || parts[0] != "Basic" {
				api.unauthorizedBasicError(w, r, fmt.Errorf("invalid Authorization header"))
				return
			}

			//* decode the base64
			decoded, err := base64.StdEncoding.DecodeString(parts[1])
			if err != nil {
				api.unauthorizedBasicError(w, r, err)
				return
			}

			username := api.config.auth.basic.username
			pwd := api.config.auth.basic.password

			//* check the credentials
			creds := strings.SplitN(string(decoded), ":", 2)
			if len(creds) != 2 || creds[0] != username || creds[1] != pwd {
				api.unauthorizedBasicError(w, r, fmt.Errorf("invalid credentials"))
				return
			}

			next.ServeHTTP(w, r)
		})
	}
}

func (api *api) AuthTokenMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		authHeader := r.Header.Get("Authorization")
		if authHeader == "" {
			api.unauthorizedError(w, r, fmt.Errorf("missing Authorization header"))
			return
		}

		parts := strings.Split(authHeader, " ")
		if len(parts) != 2 || parts[0] != "Bearer" {
			api.unauthorizedError(w, r, fmt.Errorf("invalid Authorization header"))
			return
		}

		token := parts[1]

		jwtToken, err := api.authenticator.ValidateToken(token)
		if err != nil {
			api.unauthorizedError(w, r, err)
			return
		}

		claims := jwtToken.Claims.(jwt.MapClaims)

		userID, err := strconv.ParseInt(fmt.Sprintf("%.f", claims["sub"]), 10, 64)
		if err != nil {
			api.unauthorizedError(w, r, fmt.Errorf("invalid token"))
			return
		}

		ctx := r.Context()
		user, err := api.getUser(ctx, userID)
		if err != nil {
			api.unauthorizedError(w, r, err)
			return
		}

		ctx = context.WithValue(ctx, userContextKey, user)
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

func (api *api) postsContextMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		idParam := chi.URLParam(r, "id")

		id, err := strconv.ParseInt(idParam, 10, 64)
		if err != nil {
			api.badRequestError(w, r, err)
			return
		}
		ctx := r.Context()

		post, err := api.store.Posts.GetByID(ctx, id)
		if err != nil {
			switch {
			case errors.Is(err, store.ErrNotFound):
				api.notFoundError(w, r, err)
			default:
				api.internalServerError(w, r, err)
			}
			return
		}

		ctx = context.WithValue(ctx, postCtx, post)
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

func (api *api) checkPostOwnership(requiredRole string, next http.HandlerFunc) http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		user := getUserFromContext(r)
		post := getPostFromContext(r)

		if user.ID == post.UserID {
			next.ServeHTTP(w, r)
			return
		}

		allowed, err := api.checkRolePrecedence(r.Context(), user, requiredRole)
		if err != nil {
			api.internalServerError(w, r, err)
			return
		}

		if !allowed {
			api.forbiddenError(w, r, fmt.Errorf("forbidden"))
			return
		}

		next.ServeHTTP(w, r)
	})
}

func (api *api) checkProfileOwnership(next http.HandlerFunc) http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		user := getUserFromContext(r)
		profile := getUserFromContext(r)

		if user.ID == profile.ID {
			next.ServeHTTP(w, r)
			return
		}

		api.forbiddenError(w, r, fmt.Errorf("forbidden"))
	})
}

func (api *api) checkRolePrecedence(ctx context.Context, user *store.User, roleName string) (bool, error) {
	role, err := api.store.Roles.GetByName(ctx, roleName)
	if err != nil {
		return false, err
	}

	return user.Role.Level >= role.Level, nil
}

func (api *api) getUser(ctx context.Context, id int64) (*store.User, error) {
	if !api.config.redisCfg.enabled {
		return api.store.Users.GetByID(ctx, id)
	}

	user, err := api.cacheStore.Users.Get(ctx, id)
	if err != nil {
		return nil, err
	}

	if user == nil {
		user, err = api.store.Users.GetByID(ctx, id)
		if err != nil {
			return nil, err
		}

		if err := api.cacheStore.Users.Set(ctx, user); err != nil {
			return nil, err
		}
	}

	return user, nil
}

func (api *api) RateLimiterMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if api.config.rateLimiter.Enabled {
			if allow, retryAfter := api.rateLimiter.Allow(r.RemoteAddr); !allow {
				api.rateLimitExceededError(w, r, retryAfter.String())
				return
			}
		}

		next.ServeHTTP(w, r)
	})
}
