package internal

import (
	"context"
	"errors"
	"fmt"

	api "git.vdhsn.com/barretthousen/barretthousen/src/auth/api/grpc"
	"git.vdhsn.com/barretthousen/barretthousen/src/auth/internal/domain"
	"git.vdhsn.com/barretthousen/barretthousen/src/lib/kernel"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/timestamppb"
)

func NewAuthServer(d *domain.Domain) func(grpcServer grpc.ServiceRegistrar, endpoint string) {
	return func(grpcServer grpc.ServiceRegistrar, endpoint string) {
		api.RegisterAuthServer(grpcServer, &authHandler{domain: d})
	}
}

type authHandler struct {
	api.UnimplementedAuthServer
	domain *domain.Domain
}

func (a *authHandler) CreateAccount(ctx context.Context, in *api.CreateAccountInput) (*api.Account, error) {
	kernel.TraceLog.Printf("Attempting to create user: %q - %q", in.Email, in.Password)
	account, err := a.domain.CreateAccount(ctx, domain.CreateAccountCommand{
		Email:    in.Email,
		Password: in.Password,
		Role:     domain.Role(in.Role),
	})
	if err != nil {
		return nil, err
	}

	return &api.Account{
		Id:         int32(account.ID),
		Email:      account.Email,
		Role:       account.Role.String(),
		CreatedTs:  timestamppb.New(account.Created),
		VerifiedTs: timestamppb.New(account.Verified),
	}, nil
}

func (a *authHandler) Login(ctx context.Context, in *api.LoginInput) (out *api.LoginResult, err error) {
	var result domain.LoggedIn
	if result, err = a.domain.Login(ctx, domain.LoginCommand{
		Email:    in.Email,
		Password: in.Password,
	}); errors.Is(err, domain.ErrInvalidLogin) {
		err = status.Errorf(codes.PermissionDenied, "Email or password invalid.")
		return
	} else if err != nil {
		err = fmt.Errorf("could not login: %w", err)
		return
	}

	if err = grpc.SetHeader(ctx, metadata.New(map[string]string{
		"bh-session-id": fmt.Sprintf("%d", result.SessionID),
	})); err != nil {
		err = status.Errorf(codes.Internal, "could not set header")
		return
	}

	out = &api.LoginResult{
		SessionId: fmt.Sprintf("%d", result.SessionID),
		Account: &api.Account{
			Id:         int32(result.ID),
			Email:      result.Email,
			Role:       result.Role.String(),
			CreatedTs:  timestamppb.New(result.Created),
			VerifiedTs: nil,
		},
	}
	return
}

func (a *authHandler) VerifyAccount(ctx context.Context, in *api.VerificationParameters) (*api.AccountVerifiedResult, error) {
	return nil, status.Errorf(codes.Unimplemented, "method VerifyAccount not implemented")
}

func (a *authHandler) GetAccount(ctx context.Context, in *api.AccountFilter) (*api.Account, error) {
	return nil, status.Errorf(codes.Unimplemented, "method GetAccount not implemented")
}

func (a *authHandler) GetSession(ctx context.Context, in *api.SessionFilter) (out *api.Account, err error) {
	var result domain.Account
	if result, err = a.domain.GetAccount(ctx, domain.GetAccountCommand{
		SessionID: int(in.SessionId),
	}); err != nil {
		err = status.Error(codes.Unauthenticated, "forbidden")
		return
	}

	out = &api.Account{
		Id:         int32(result.ID),
		Email:      result.Email,
		Role:       result.Role.String(),
		CreatedTs:  timestamppb.New(result.Created),
		VerifiedTs: nil,
	}

	return
}
