package serra

import (
	"fmt"
	"time"

	"github.com/mitchellh/mapstructure"
	"github.com/schollz/progressbar/v3"
	"github.com/spf13/cobra"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
)

func init() {
	rootCmd.AddCommand(updateCmd)
}

var updateCmd = &cobra.Command{
	Aliases:       []string{"u"},
	Use:           "update",
	Short:         "update card values from scryfall",
	Long:          `the update mechanism iterates over each card in your collection and fetches its price. after all cards you own in a set are updated, the set value will update. after all sets are updated, the whole collection value is updated.`,
	SilenceErrors: true,
	RunE: func(cmd *cobra.Command, args []string) error {

		client := storageConnect()
		l := Logger()
		defer storageDisconnect(client)

		// update sets
		setscoll := &Collection{client.Database("serra").Collection("sets")}
		coll := &Collection{client.Database("serra").Collection("cards")}
		totalcoll := &Collection{client.Database("serra").Collection("total")}

		// predefine query for set analysis. used for total stats later
		projectStage := bson.D{{"$project",
			bson.D{
				{"serra_count", true},
				{"serra_count_foil", true},
				{"set", true},
				{"last_price", bson.D{{"$arrayElemAt", bson.A{"$serra_prices", -1}}}}}}}
		groupStage := bson.D{
			{"$group", bson.D{
				{"_id", ""},
				{"eur", bson.D{{"$sum", bson.D{{"$multiply", bson.A{"$last_price.eur", "$serra_count"}}}}}},
				{"eurfoil", bson.D{{"$sum", bson.D{{"$multiply", bson.A{"$last_price.eur_foil", "$serra_count_foil"}}}}}},
				{"usd", bson.D{{"$sum", bson.D{{"$multiply", bson.A{"$last_price.usd", "$serra_count"}}}}}},
				{"usdfoil", bson.D{{"$sum", bson.D{{"$multiply", bson.A{"$last_price.usd_foil", "$serra_count_foil"}}}}}},
			}}}

		l.Info("Fetching bulk data from scryfall...")
		downloadURL, err := fetchBulkDownloadURL()
		if err != nil {
			l.Error("Could not extract bulk download URL:", err)
			return err
		}
		l.Infof("Found latest bulkfile url: %s", downloadURL)

		l.Info("Downloading bulk data file...")
		bulkFilePath, err := downloadBulkData(downloadURL)
		if err != nil {
			l.Error("Could not fetch bulk json from scryfall", err)
			return err
		}

		l.Info("Loading bulk data file...")
		updatedCards, err := loadBulkFile(bulkFilePath)
		if err != nil {
			l.Error("Could not load bulk file:", err)
			return err
		}
		l.Infof("Successfully loaded %d cards. Starting Update.", len(updatedCards))

		sets, _ := fetchSets()
		for _, set := range sets.Data {

			// When downloading new sets, PriceList needs to be initialized
			// This query silently fails if set was already downloaded. Not nice but ok for now.
			// TODO: make this not fail silently
			set.SerraPrices = []PriceEntry{}
			setscoll.storageAddSet(&set)

			cards, _ := coll.storageFind(bson.D{{"set", set.Code}}, bson.D{{"_id", 1}}, 0, 0)

			// if no cards in collection for this set, skip it
			if len(cards) == 0 {
				continue
			}

			bar := progressbar.NewOptions(len(cards),
				progressbar.OptionSetWidth(50),
				progressbar.OptionSetDescription(fmt.Sprintf("%s, %s%s%s\t", set.ReleasedAt[0:4], Yellow, set.Code, Reset)),
				progressbar.OptionEnableColorCodes(true),
				progressbar.OptionShowCount(),
				progressbar.OptionSetTheme(progressbar.Theme{
					Saucer:        "[green]=[reset]",
					SaucerHead:    "[green]>[reset]",
					SaucerPadding: " ",
					BarStart:      "|",
					BarEnd:        "| " + set.Name,
				}),
			)

			for _, card := range cards {
				bar.Add(1)
				updatedCard, err := getCardFromBulk(updatedCards, card.Set, card.CollectorNumber)
				if err != nil {
					l.Error(err)
					continue
				}

				updatedCard.Prices.Date = primitive.NewDateTimeFromTime(time.Now())

				update := bson.M{
					"$set":  bson.M{"serra_updated": primitive.NewDateTimeFromTime(time.Now()), "prices": updatedCard.Prices, "cmc": updatedCard.Cmc, "cardmarketid": updatedCard.CardmarketID, "tcgplayerid": updatedCard.TCGPlayerID},
					"$push": bson.M{"serra_prices": updatedCard.Prices},
				}
				coll.storageUpdate(bson.M{"_id": bson.M{"$eq": card.ID}}, update)
			}
			fmt.Println()

			// update set value sum

			// calculate value summary
			matchStage := bson.D{{"$match", bson.D{{"set", set.Code}}}}
			setValue, _ := coll.storageAggregate(mongo.Pipeline{matchStage, projectStage, groupStage})

			p := PriceEntry{}
			s := setValue[0]

			p.Date = primitive.NewDateTimeFromTime(time.Now())

			// fill struct PriceEntry with map from mongoresult
			mapstructure.Decode(s, &p)

			// do the update
			setUpdate := bson.M{
				"$set":  bson.M{"serra_updated": p.Date, "cardcount": set.CardCount},
				"$push": bson.M{"serra_prices": p},
			}
			setscoll.storageUpdate(bson.M{"code": bson.M{"$eq": set.Code}}, setUpdate)
		}

		totalValue, _ := coll.storageAggregate(mongo.Pipeline{projectStage, groupStage})

		t := PriceEntry{}
		t.Date = primitive.NewDateTimeFromTime(time.Now())
		mapstructure.Decode(totalValue[0], &t)

		// This is here to be able to fetch currency from
		// constructed new priceentry
		tmpCard := Card{}
		tmpCard.Prices = t

		l.Info("\n%sUpdating total value of collection to: %s%.02f%s%s\n", Green, Yellow, tmpCard.getValue(false)+tmpCard.getValue(true), getCurrency(), Reset)
		totalcoll.storageAddTotal(t)

		return nil
	},
}
