package historycmd

import (
	"context"
	"encoding/csv"
	"encoding/json"
	"flag"
	"io"
	"net/http"
	"os"
	"time"

	"code.justin.tv/foundation/history-admin/internal/awssig"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/google/subcommands"
	elastic "gopkg.in/olivere/elastic.v5"
)

const timeFormat = "2006-01-02"
const esTimeFormat = "2006-01-02T15:04:05.000000000Z07:00"

// DumpCmd ...
type DumpCmd struct {
	*BaseCmd

	elastic      *elastic.Client
	userID       string
	startDateRaw string
	endDateRaw   string
	startDate    *time.Time
	endDate      *time.Time
}

// Name implements subcommands
func (cmd DumpCmd) Name() string {
	return "dump"
}

// Synopsis implements subcommands
func (cmd DumpCmd) Synopsis() string {
	return "dump history records as csv"
}

// Usage implements subcommands
func (cmd DumpCmd) Usage() string {
	return `dump -start-date [start] -end-date [end] -user-id [user-id] > out.csv
`
}

// SetFlags implements subcommands
func (cmd *DumpCmd) SetFlags(f *flag.FlagSet) {
	f.StringVar(&cmd.userID, "userid", "", "userid to filter for.")
	f.StringVar(&cmd.startDateRaw, "start-date", "", "only dump events past this date (inclusive).")
	f.StringVar(&cmd.endDateRaw, "end-date", "", "only dump events before this date (inclusive).")
}

// Execute implements subcommands
func (cmd DumpCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
	if err := cmd.init(ctx); err != nil {
		cmd.Logger.Error(err)
		return subcommands.ExitFailure
	}

	if err := cmd.execute(ctx); err != nil {
		cmd.Logger.Error(err)
		return subcommands.ExitFailure
	}

	return subcommands.ExitSuccess
}

func (cmd *DumpCmd) init(ctx context.Context) error {
	elasticSearchClient, err := cmd.elasticSearchClient()
	if err != nil {
		return err
	}
	cmd.elastic = elasticSearchClient

	if len(cmd.startDateRaw) > 0 {
		startDate, err := time.Parse(timeFormat, cmd.startDateRaw)
		if err != nil {
			return err
		}
		cmd.startDate = &startDate
	}

	if len(cmd.endDateRaw) > 0 {
		endDate, err := time.Parse(timeFormat, cmd.endDateRaw)
		if err != nil {
			return err
		}
		cmd.endDate = &endDate
	}

	return nil
}

// ConfigureClient configures the elasticsearch client with a chain of role arns
func (cmd DumpCmd) elasticSearchClient() (*elastic.Client, error) {
	elasticSearchRoleArn, err := cmd.ElasticSearchRoleArn()
	if err != nil {
		return nil, err
	}

	elasticSearchURL, err := cmd.ElasticSearchURL()
	if err != nil {
		return nil, err
	}

	return elastic.NewSimpleClient(
		elastic.SetURL(elasticSearchURL),
		elastic.SetHttpClient(&http.Client{
			Transport: &awssig.RoundTripper{
				AWSService: "es",
				Credentials: stscreds.NewCredentials(session.Must(session.NewSession(&aws.Config{
					Region:      aws.String("us-west-2"),
					Credentials: nil,
				})), elasticSearchRoleArn),
			},
		}),
	)
}

func (cmd DumpCmd) execute(ctx context.Context) error {
	query, err := cmd.query()
	if err != nil {
		return err
	}

	scroll := cmd.elastic.Scroll().
		Index("history-*").
		Type("audits").
		Query(query).
		Size(100)

	w := csv.NewWriter(os.Stdout)
	defer func() {
		w.Flush()
	}()

	if err := w.Write([]string{
		"uuid",
		"action",
		"created_at",
		"description",
		"resource_type",
		"resource_id",
		"user_type",
		"user_id",
		"changes",
	}); err != nil {
		return err
	}

	for {
		page, err := scroll.Do(ctx)
		if err == io.EOF {
			return nil
		} else if err != nil {
			return err
		}

		cmd.Logger.Infof("got %d hits\n", len(page.Hits.Hits))
		for _, hit := range page.Hits.Hits {
			audit := struct {
				UUID         string `json:"uuid"`
				Action       string `json:"action"`
				CreatedAt    string `json:"created_at"`
				Description  string `json:"description"`
				ResourceType string `json:"resource_type"`
				ResourceID   string `json:"resource_id"`
				UserType     string `json:"user_type"`
				UserID       string `json:"user_id"`
				Changes      []struct {
					Attribute string `json:"attribute"`
					OldValue  string `json:"old_value"`
					NewValue  string `json:"new_value"`
				} `json:"changes"`
			}{}

			if err := json.Unmarshal(*hit.Source, &audit); err != nil {
				return err
			}

			rawChanges, err := json.Marshal(audit.Changes)
			if err != nil {
				return err
			}
			if err := w.Write([]string{
				audit.UUID,
				audit.Action,
				audit.CreatedAt,
				audit.Description,
				audit.ResourceType,
				audit.ResourceID,
				audit.UserType,
				audit.UserID,
				string(rawChanges),
			}); err != nil {
				return err
			}
		}
	}
}

func (cmd DumpCmd) query() (*elastic.BoolQuery, error) {
	q := elastic.NewBoolQuery().
		Filter(elastic.NewTermsQuery("user_id", cmd.userID))
	return cmd.applyRangeQuery(q)
}

func (cmd DumpCmd) applyRangeQuery(query *elastic.BoolQuery) (*elastic.BoolQuery, error) {
	if cmd.startDate == nil && cmd.endDate == nil {
		return query, nil
	}

	rq := elastic.NewRangeQuery("created_at")
	if cmd.startDate != nil {
		rq = rq.Gte(cmd.startDate.Format(esTimeFormat))
	}

	if cmd.endDate != nil {
		rq = rq.Lt(cmd.endDate.Add(24 * time.Hour).Format(esTimeFormat))
	}

	return query.Filter(rq), nil
}
