package service

import (
	authservice2 "authservice/api/authservice"
	"authservice/internal/repository"
	"authservice/internal/tools"
	"context"
	"errors"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
)

type AuthService struct {
	Repository *repository.AuthRepository
	Crypto     *repository.CryptoRepository
}

const (
	Authorization = "Authorization"
	Signature     = "Signature"
	Digest        = "Digest"
)

func (service *AuthService) SignIn(context context.Context, request *authservice2.CredentialsRequest) (*authservice2.CredentialsResponse, error) {
	_, err := service.Crypto.ParseKey(request.Key)
	if err != nil {
		return nil, err
	}
	user, err := service.Repository.CheckIfUserExists(context, request.Email)
	if err != nil {
		return nil, err
	}
	if user.Password != request.Password {
		return nil, tools.CreateError(codes.Unauthenticated, "Invalid credentials provided", "Wrong password")
	}

	user, err = service.Repository.UpdateUser(context, user, request.Key)
	if err != nil {
		return nil, err
	}
	jwt, err := service.Crypto.CreateJWT(user.Id.Value, request.Key)
	if err != nil {
		return nil, err
	}
	return &authservice2.CredentialsResponse{
		Jwt: jwt,
	}, nil
}

func (service *AuthService) SignUp(context context.Context, request *authservice2.CredentialsRequest) (*authservice2.CredentialsResponse, error) {
	user, err := service.Repository.CheckIfUserExists(context, request.Email)
	if err != nil && status.Convert(err).Code() != codes.NotFound {
		return nil, err
	}

	if user != nil {
		return nil, tools.CreateError(codes.AlreadyExists, "User with provided email already exists", "User exists")
	}

	_, err = service.Crypto.ParseKey(request.Key)

	if err != nil {
		return nil, err
	}

	user, err = service.Repository.CreateUser(context, request.Email, request.Password, request.Key)

	if err != nil {
		return nil, err
	}

	jwt, err := service.Crypto.CreateJWT(user.Id.Value, request.Key)

	if err != nil {
		return nil, err
	}

	return &authservice2.CredentialsResponse{
		Jwt: jwt,
	}, nil
}

func (service *AuthService) SignOut(context context.Context, request *authservice2.Request) (*authservice2.Response, error) {
	md, ok := metadata.FromIncomingContext(context)
	if !ok {
		return nil, tools.CreateError(codes.Unauthenticated, "Can`t get metadata from context", "Missing JWT")
	}
	jwt := md.Get(Authorization)
	if len(jwt) == 0 {
		return nil, tools.CreateError(codes.Unauthenticated, "Can`t get jwt from metadata", "Missing JWT")
	}
	token, err := service.Crypto.ParseJWTToken(jwt[0])
	if err != nil {
		return nil, err
	}

	err = service.Crypto.VerifyJWT(token, false)
	if err != nil {
		return nil, err
	}

	claims, err := service.Crypto.GetJWTClaims(token)
	if err != nil {
		return nil, err
	}
	userId := claims[repository.UserIdKey].(string)
	userObjectId, err := primitive.ObjectIDFromHex(userId)
	if err != nil {
		return nil, tools.ConvertError(codes.InvalidArgument, "User id is not in valid format", err)
	}
	user, err := service.Repository.GetUserById(context, userObjectId)
	if err != nil {
		return nil, err
	}

	_, err = service.Repository.UpdateUser(context, user, "")
	if err != nil {
		return nil, err
	}
	return &authservice2.Response{}, nil
}

func (service *AuthService) Verify(context context.Context, _ *authservice2.Request) (*authservice2.VerifyResponse, error) {
	md, ok := metadata.FromIncomingContext(context)
	if !ok {
		return nil, tools.CreateError(codes.Unauthenticated, "Can`t get metadata from context", "Missing JWT")
	}

	jwt := md.Get(Authorization)
	if len(jwt) == 0 {
		return nil, tools.CreateError(codes.Unauthenticated, "Can`t get jwt from metadata", "Missing JWT")
	}
	signature := md.Get(Signature)
	if len(signature) == 0 {
		return nil, tools.CreateError(codes.Unauthenticated, "Can`t get signature from metadata", "Missing Signature")
	}
	digest := md.Get(Digest)
	if len(digest) == 0 {
		return nil, tools.CreateError(codes.Unauthenticated, "Can`t get digest from metadata", "Missing Digest")
	}

	token, err := service.Crypto.ParseJWTToken(jwt[0])
	if err != nil {
		return nil, err
	}
	err = service.Crypto.VerifyJWT(token, false)
	if err != nil {
		return nil, err
	}
	valid, err := service.Crypto.VerifyRequestSignature(token, digest[0], signature[0], jwt[0])
	if err != nil {
		return nil, err
	}
	claims, err := service.Crypto.GetJWTClaims(token)
	if err != nil {
		return nil, err
	}
	userId := claims[repository.UserIdKey].(string)
	return &authservice2.VerifyResponse{
		Valid:  valid,
		UserId: userId,
	}, nil
}

func (service *AuthService) RenewToken(context context.Context, request *authservice2.RenewRequest) (*authservice2.CredentialsResponse, error) {
	token, err := service.Crypto.ParseJWTToken(request.RefreshToken)
	if err != nil {
		return nil, err
	}
	err = service.Crypto.VerifyJWT(token, true)
	if err != nil {
		return nil, err
	}
	claims, _ := service.Crypto.GetJWTClaims(token)
	userId := claims[repository.UserIdKey].(string)
	pubKey := claims[repository.PublicKeyKey].(string)
	objectId, err := primitive.ObjectIDFromHex(userId)
	if err != nil {
		return nil, err
	}
	user, err := service.Repository.GetUserById(context, objectId)

	if err != nil {
		return nil, err
	}

	if user.Key != pubKey {
		return nil, errors.New("invalid provided public key")
	}

	jwt, err := service.Crypto.CreateJWT(userId, pubKey)
	if err != nil {
		return nil, err
	}

	return &authservice2.CredentialsResponse{
		Jwt: jwt,
	}, nil
}

func (service *AuthService) FindUser(context context.Context, request *authservice2.FindUserRequest) (*authservice2.FindUserResponse, error) {
	user, err := service.Repository.CheckIfUserExists(context, request.Email)
	if err != nil {
		return nil, err
	}
	return &authservice2.FindUserResponse{
		UserId: user.Id.Value,
	}, nil
}
