package main

import (
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/golang/protobuf/proto"
	"github.com/pkg/errors"
	"github.com/satori/go.uuid"
	"github.com/urfave/cli"

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

func init() {
	var client *event.Client
	cmd := cli.Command{
		Name:  "events",
		Usage: "commands related to the events service",
		Before: func(c *cli.Context) error {
			var err error
			client, err = event.NewClient(c.GlobalString("rps-addr"))
			if err != nil {
				return err
			}
			return nil
		},
		After: func(c *cli.Context) error {
			if client == nil {
				return nil
			}
			if err := client.Close(); err != nil {
				log.Error(err)
			}
			return nil
		},
		Subcommands: []cli.Command{
			{
				Name:      "add",
				Usage:     "add event with a text-formatted protobuf file",
				ArgsUsage: "FILE",
				Action: func(c *cli.Context) error {
					protoFilename := c.Args().Get(0)
					if len(protoFilename) == 0 {
						return errors.New(
							"filename for a text-formated Event protobuf is required")
					}
					protoBytes, err := ioutil.ReadFile(protoFilename)
					if err != nil {
						return errors.Wrapf(
							err, "Error reading '%s'", protoFilename)
					}
					userEvent := &pb.Event{}
					err = proto.UnmarshalText(string(protoBytes[:]), userEvent)
					if err != nil {
						return err
					}
					// Set some sane defaults so user doesn't have to.
					event := &pb.Event{
						Uuid:      uuid.NewV4().Bytes(),
						Timestamp: proto.Float64(convertTimestamp(time.Now())),
					}
					proto.Merge(event, userEvent)
					return addEvent(client, event)
				},
			},
			{
				Name:      "get",
				Usage:     "get event metatdata of given event ids",
				ArgsUsage: "UUID",
				Flags: []cli.Flag{
					cli.BoolFlag{
						Name:  "body-only",
						Usage: "Just print the body",
					},
					cli.BoolFlag{
						Name:  "text-proto",
						Usage: "Print the event as a text-formatted protobuffer",
					},
				},
				Action: func(c *cli.Context) error {
					uuidFlag := c.Args().Get(0)
					if len(uuidFlag) == 0 {
						return errors.New("event UUID argument is required")
					}
					uuid, err := base64.StdEncoding.DecodeString(uuidFlag)
					if err != nil {
						return errors.Wrap(err, "UUID isn't valid base64")
					}
					ep := &eventPrinter{
						bodyOnly:  c.Bool("body-only"),
						textProto: c.Bool("text-proto"),
					}
					return printEvent(client, uuid, ep)
				},
			},
			{
				Name:  "query",
				Usage: "search for events",
				Flags: []cli.Flag{
					cli.DurationFlag{
						Name:  "period",
						Usage: "how far back to query for",
						Value: 1 * time.Hour,
					},
					cli.StringFlag{
						Name:  "type",
						Usage: "limit queries to this event type",
					},
					cli.StringSliceFlag{
						Name:  "filter",
						Usage: "attribute=value, filters for attribute matching value. Can be specified multiple times.",
					},
					cli.BoolFlag{
						Name:  "body-only",
						Usage: "Just print the body",
					},
					cli.BoolFlag{
						Name:  "text-proto",
						Usage: "Print the event as a text-formatted protobuffer",
					},
				},
				Action: func(c *cli.Context) error {
					filters := make(map[string]string)
					for _, arg := range c.StringSlice("filter") {
						parts := strings.SplitN(arg, "=", 2)
						if len(parts) < 2 {
							return cli.NewExitError(
								"--filter requires attribute=value format", 1)
						}
						filters[parts[0]] = parts[1]
					}
					ep := &eventPrinter{
						bodyOnly:  c.Bool("body-only"),
						textProto: c.Bool("text-proto"),
					}
					return printEventQuery(
						client, c.Duration("period"), c.String("type"), filters, ep)
				},
			},
		},
	}
	app.Commands = append(app.Commands, cmd)
}

// Convert Go Time type into Unix epoch secs + fractional seconds as float64.
func convertTimestamp(timestamp time.Time) float64 {
	return float64(timestamp.UnixNano()) * 1E-9
}

func addEvent(client *event.Client, event *pb.Event) error {
	err := client.AddEvent(event)
	if err != nil {
		return err
	}
	return nil
}

func printEvent(client *event.Client, uuid []byte, ep *eventPrinter) error {
	event, err := client.GetEvent(uuid)
	if err != nil {
		return err
	}
	ep.PrintEvent(event)
	return nil
}

func printEventQuery(client *event.Client, period time.Duration, eventType string, filters map[string]string, ep *eventPrinter) error {
	end := time.Now()
	start := end.Add(-period)
	events, err := client.QueryEvents(start, end, eventType, filters)
	if err != nil {
		return err
	}
	for _, event := range events {
		ep.PrintEvent(event)
	}
	return nil
}

type eventPrinter struct {
	bodyOnly  bool
	textProto bool
}

func (ep *eventPrinter) PrintEvent(event *pb.Event) {
	if ep.bodyOnly {
		fmt.Print(string(event.GetBody()))
	} else {
		if ep.textProto {
			fmt.Print(proto.MarshalTextString(event))
		} else {
			fmt.Println("UUID: ", base64.StdEncoding.EncodeToString(event.GetUuid()))
			fmt.Println("Timestamp: ", time.Unix(int64(event.GetTimestamp()), 0))
			fmt.Println("Type: ", event.GetType())
			fmt.Printf("Body: %q\n", string(event.GetBody()))
			if len(event.GetAttributes()) > 0 {
				fmt.Println("Attributes: ")
				for _, attribute := range event.GetAttributes() {
					fmt.Println("\t", attribute.GetKey(), "=", attribute.GetValue())
				}
			}
		}
	}
}
