package utils

import (
	"context"
	"fmt"
	"net/http"
	"strings"

	"github.com/deepmap/oapi-codegen/pkg/middleware"
	"github.com/getkin/kin-openapi/openapi3filter"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
)

const (
	prefix = "Bearer "
)

func NewAuthenticator(jwtUtil *JWT) openapi3filter.AuthenticationFunc {
	return func(ctx context.Context, input *openapi3filter.AuthenticationInput) error {
		if jwtUtil == nil {
			return errors.New(
				fmt.Errorf("jwt util is nil: %w", errors.ErrInvalidArgs),
				errors.ErrInvalidArgs,
			)
		}

		return authenticate(ctx, jwtUtil, input)
	}
}

func authenticate(ctx context.Context, jwtUtil *JWT, input *openapi3filter.AuthenticationInput) error {
	if input.SecuritySchemeName != "Bearer" {
		return fmt.Errorf("security scheme %s != 'Bearer'", input.SecuritySchemeName)
	}

	// Now, we need to get the JWS from the request, to match the request expectations
	// against request contents.
	jws, err := parseJWSFromRequest(input.RequestValidationInput.Request)
	if err != nil {
		return err
	}

	// if the JWS is valid, we have a JWT, which will contain a bunch of claims.
	userID, validateErr := jwtUtil.Validate(jws)
	if validateErr != nil {
		return validateErr
	}

	// Set the property on the echo context so the handler is able to
	// access the claims data we generate in here.
	echoCtx := middleware.GetEchoContext(ctx)

	echoCtx.Set(models.AuthJWTDecodedUserIDKey, userID)
	echoCtx.Set(models.AuthJWTDecodedAccessTokenKey, jws)

	return nil
}

// extracts a JWS string from an Authorization: Bearer <jws> header.
func parseJWSFromRequest(request *http.Request) (string, errors.Error) {
	header := request.Header.Get("Authorization")

	if header == "" || !strings.HasPrefix(header, prefix) {
		return "", errors.New(
			fmt.Errorf("failed to parse jws from request header: %s", header),
			errors.ErrNoAccess,
		)
	}

	return strings.TrimPrefix(header, prefix), nil
}
