package event

import (
	"encoding/base64"

	xnetcontext "golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"

	pb "code.justin.tv/dta/rockpaperscissors/proto"
)

var (
	errf = grpc.Errorf

	// ErrEventNotSet is returned when validating an event and it is nil.
	ErrEventNotSet = errf(
		codes.InvalidArgument, "Required field event wasn't set.")
	// ErrUUIDNotSet is returned when validating an event and the uuid isn't set.
	ErrUUIDNotSet = errf(codes.InvalidArgument,
		"Required field uuid or encoded_uuid wasn't set on event.")
	// ErrTimestampNotSet is returned when validating an event and the timestamp isn't set.
	ErrTimestampNotSet = errf(
		codes.InvalidArgument, "Required field timestamp wasn't set on event.")
	// ErrTypeNotSet is returned when validating an event and the type isn't set.
	ErrTypeNotSet = errf(
		codes.InvalidArgument, "Required field type wasn't set on event.")
	// ErrBodyNotSet is returned when validating an event and the body isn't set.
	ErrBodyNotSet = errf(
		codes.InvalidArgument, "Required field body wasn't set on event.")

	// ErrNoUUID is returned when requesting GetEvent without a UUID.
	ErrNoUUID = errf(codes.InvalidArgument, "No uuid was given.")

	// ErrTimeRangeNotSet is returned when either a start or end time isn't specified.
	ErrTimeRangeNotSet = errf(
		codes.InvalidArgument, "Both a start and end time are required.")
	// ErrQueryTypeParamNotSet is returned when type field is not set.
	ErrQueryTypeParamNotSet = errf(codes.InvalidArgument,
		"Type query field must be set.")

	// ErrEventNotFound if returned when the given event UUID isn't in the datastore.
	ErrEventNotFound = errf(codes.NotFound, "Event not found.")
)

// Server is a gRPC service server implementing eventServiceServer.
type Server struct {
	datastore Datastore
}

// NewServer constructs a event gRPC Server.
func NewServer(datastore Datastore) *Server {
	return &Server{
		datastore: datastore,
	}
}

func validateEvent(event *pb.Event) error {
	if event == nil {
		return ErrEventNotSet
	}
	if event.GetUuid() == nil {
		return ErrUUIDNotSet
	}
	if event.GetTimestamp() == 0 {
		return ErrTimestampNotSet
	}
	if event.GetType() == "" {
		return ErrTypeNotSet
	}
	if event.GetBody() == nil {
		return ErrBodyNotSet
	}
	return nil
}

// AddEvent adds an event to the datastore.
func (s *Server) AddEvent(ctx xnetcontext.Context, req *pb.AddEventRequest) (*pb.AddEventResponse, error) {
	event := req.GetEvent()
	if err := validateEvent(event); err != nil {
		return nil, err
	}

	if err := s.datastore.Put(ctx, event); err != nil {
		return nil, errf(codes.Internal, "Failed to put item: %v", err)
	}

	return &pb.AddEventResponse{}, nil
}

// GetEvent returns a list of selected events by uuid.
func (s *Server) GetEvent(ctx xnetcontext.Context, req *pb.GetEventRequest) (*pb.GetEventResponse, error) {
	uuid := req.Uuid
	if len(uuid) == 0 {
		if len(req.EncodedUuid) > 0 {
			var err error
			uuid, err = base64.StdEncoding.DecodeString(req.EncodedUuid)
			if err != nil {
				return nil, errf(codes.InvalidArgument,
					"Could not base64 decode encoded_uuid field: %v", err)
			}
		} else {
			return nil, ErrNoUUID
		}
	}

	event, err := s.datastore.Get(ctx, uuid)
	if err != nil {
		return nil, errf(codes.Internal, "%v", err)
	}

	return &pb.GetEventResponse{Event: event}, nil
}

// QueryEvents returns a selected list of Events from the datastore.
func (s *Server) QueryEvents(ctx xnetcontext.Context, req *pb.QueryEventsRequest) (*pb.QueryEventsResponse, error) {
	if req.Timerange == nil || req.Timerange.Start.Seconds == 0 ||
		req.Timerange.End.Seconds == 0 {
		return nil, ErrTimeRangeNotSet
	}
	if req.Type == "" {
		return nil, ErrQueryTypeParamNotSet
	}

	q := &pb.EventDatastoreQuery{
		Type:         req.Type,
		StartSeconds: req.Timerange.Start.Seconds,
		EndSeconds:   req.Timerange.End.Seconds,
		Filter:       make(map[string]string),
	}

	for _, f := range req.Filters {
		q.Filter[f.Key] = f.Value
	}

	results, err := s.datastore.Query(ctx, q)
	if err != nil {
		return nil, errf(codes.Internal, "%v", err)
	}

	if len(results.Events) == 0 {
		return nil, ErrEventNotFound
	}

	return &pb.QueryEventsResponse{
		TotalEvents: int64(len(results.Events)),
		Events:      results.Events,
	}, nil
}
