package serra

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"image/png"
	"os"
	"sort"
	"strings"

	"github.com/dolmen-go/kittyimg"
	"github.com/nfnt/resize"
	"github.com/spf13/cobra"
	"go.mongodb.org/mongo-driver/bson"
)

func init() {
	cardCmd.Flags().StringVarP(&artist, "artist", "a", "", "Filter by name of artist")
	cardCmd.Flags().StringVarP(&rarity, "rarity", "r", "", "Filter by rarity of cards (mythic, rare, uncommon, common)")
	cardCmd.Flags().StringVarP(&set, "set", "e", "", "Filter by set code (usg/mmq/vow)")
	cardCmd.Flags().StringVarP(&sortby, "sort", "s", "name", "How to sort cards (value/number/name/added)")
	cardCmd.Flags().StringVarP(&name, "name", "n", "", "Name of the card (regex compatible)")
	cardCmd.Flags().Int64VarP(&cmc, "cmc", "m", -1, "Cumulative mana cost of card")
	cardCmd.Flags().StringVarP(&color, "color", "i", "", "Color identity of card (w,u,b,r,g)")
	cardCmd.Flags().StringArrayVarP(&oracle, "oracle", "o", []string{}, "Contains string in card text")
	cardCmd.Flags().StringVarP(&cardType, "type", "t", "", "Contains string in card type line")
	cardCmd.Flags().Int64VarP(&count, "min-count", "c", 0, "Occource more than X in your collection")
	cardCmd.Flags().BoolVarP(&detail, "detail", "d", false, "Show details for cards (url)")
	cardCmd.Flags().BoolVarP(&reserved, "reserved", "w", false, "If card is on reserved list")
	cardCmd.Flags().BoolVarP(&foil, "foil", "f", false, "If card is foil list")
	cardCmd.Flags().BoolVarP(&drawImg, "image", "g", false, "Draw card image using kitty format")
	cardCmd.Flags().BoolVarP(&omitInDeck, "omit-in-deck", "q", false, "Omit cards that are in decks")
	rootCmd.AddCommand(cardCmd)
}

var cardCmd = &cobra.Command{
	Aliases: []string{"cards"},
	Use:     "card [card]",
	Short:   "Search & show cards from your collection",
	Long: `Search and show cards from your collection.
If you directly put a card as an argument, it will be displayed
otherwise you'll get a list of cards as a search result.`,
	SilenceErrors: true,
	RunE: func(cmd *cobra.Command, cards []string) error {
		if len(cards) == 0 {
			cardList := Cards(rarity, set, sortby, name, oracle, cardType, reserved, foil, 0, 0, omitInDeck)
			showCardList(cardList, detail)
		} else {
			ShowCard(cards)
		}
		return nil
	},
}

func ShowCard(cardids []string) {
	client := storageConnect()
	coll := &Collection{client.Database("serra").Collection("cards")}
	l := Logger()
	defer storageDisconnect(client)

	for _, v := range cardids {
		if len(strings.Split(v, "/")) < 2 || strings.Split(v, "/")[1] == "" {
			l.Warnf("Invalid card %s", v)
			continue
		}

		cards, _ := coll.storageFind(bson.D{{"set", strings.Split(v, "/")[0]}, {"collectornumber", strings.Split(v, "/")[1]}}, bson.D{{"name", 1}}, 0, 0)

		for _, card := range cards {
			showCardDetails(&card)
		}
	}
}

func Cards(rarity, set, sortby, name string, oracle []string, cardType string, reserved, foil bool, skip, limit int64, omitInDeck bool) []Card {
	client := storageConnect()
	coll := &Collection{client.Database("serra").Collection("cards")}
	defer storageDisconnect(client)

	filter := bson.D{}

	switch rarity {
	case "uncommon":
		filter = append(filter, bson.E{"rarity", "uncommon"})
	case "common":
		filter = append(filter, bson.E{"rarity", "common"})
	case "rare":
		filter = append(filter, bson.E{"rarity", "rare"})
	case "mythic":
		filter = append(filter, bson.E{"rarity", "mythic"})
	}

	var sortStage bson.D
	switch sortby {
	case "value":
		if getCurrency() == EUR {
			sortStage = bson.D{{"prices.eur", 1}}
		} else {
			sortStage = bson.D{{"prices.usd", 1}}
		}
	case "number":
		sortStage = bson.D{{"collectornumber", 1}}
	case "name":
		sortStage = bson.D{{"name", 1}}
	case "added":
		sortStage = bson.D{{"serra_created", 1}}
	default:
		sortStage = bson.D{{"name", 1}}
	}

	if len(set) > 0 {
		filter = append(filter, bson.E{"set", set})
	}

	if len(name) > 0 {
		filter = append(filter, bson.E{"name", bson.D{{"$regex", ".*" + name + ".*"}, {"$options", "i"}}})
	}

	if len(artist) > 0 {
		filter = append(filter, bson.E{"artist", bson.D{{"$regex", ".*" + artist + ".*"}, {"$options", "i"}}})
	}

	if cmc > -1 {
		filter = append(filter, bson.E{"cmc", cmc})
	}

	for _, o:= range oracle {
		if len(o) > 0 {
			filter = append(filter, bson.E{"oracletext", bson.D{{"$regex", ".*" + o + ".*"}, {"$options", "i"}}})
		}
	}

	if len(cardType) > 0 {
		filter = append(filter, bson.E{"typeline", bson.D{{"$regex", ".*" + cardType + ".*"}, {"$options", "i"}}})
	}

	if len(color) > 0 {
		colorArr := strings.Split(strings.ToUpper(color), ",")
		filter = append(filter, bson.E{"coloridentity", colorArr})
	}

	if reserved {
		filter = append(filter, bson.E{"reserved", true})
	}

	if foil {
		filter = append(filter, bson.E{"serra_count_foil", bson.D{{"$gt", 0}}})
	}

	if omitInDeck {
		filter = append(filter, bson.E{ "$expr", bson.D{{"$or",bson.A{
			bson.D{{"$ne", bson.A{"$serra_count", "$serra_count_deck"}}},
			bson.D{{"$ne", bson.A{"$serra_count_foil", "$serra_count_foil_deck"}}},
		}}}})
	}

	cards, _ := coll.storageFind(filter, sortStage, skip, limit)

	// This is needed because collectornumbers are strings (ie. "23a") but still we
	// want it to be sorted numerically ... 1,2,3,10,11,100.
	if sortby == "number" {
		sort.Slice(cards, func(i, j int) bool {
			return filterForDigits(cards[i].CollectorNumber) < filterForDigits(cards[j].CollectorNumber)
		})
	}

	// filter out cards that do not reach the minimum amount (--min-count)
	// this is done after query result because find query constructed does not support
	// aggregating fields (of count and countFoil).
	temp := cards[:0]
	for _, card := range cards {
		if (card.SerraCount + card.SerraCountFoil) >= count {
			temp = append(temp, card)
		}
	}
	cards = temp

	return cards
}

func showCardList(cards []Card, detail bool) {

	var total float64
	if drawImg {
		for _, card := range cards {
			drawImage(&card)
		}
	} else if detail {
		for _, card := range cards {
			fmt.Printf("* %dx %s%s%s (%s/%s) %s%.2f%s %s %s %s\n", card.SerraCount+card.SerraCountFoil+card.SerraCountEtched, Purple, card.Name, Reset, card.Set, card.CollectorNumber, Yellow, card.getValue(false), getCurrency(), Background, strings.Replace(card.ScryfallURI, "?utm_source=api", "", 1), Reset)
			total = total + card.getValue(false)*float64(card.SerraCount) + card.getValue(true)*float64(card.SerraCountFoil)
		}
	} else {
		for _, card := range cards {
			fmt.Printf("* %dx (%dx) %s%s%s (%s/%s) %s%.2f%s%s\n",
				card.SerraCount+card.SerraCountFoil+card.SerraCountEtched-card.SerraCountDeck-card.SerraCountFoilDeck-card.SerraCountEtchedDeck,
				card.SerraCount+card.SerraCountFoil+card.SerraCountEtched,
				Purple, card.Name, Reset, card.Set, card.CollectorNumber, Yellow, card.getValue(false), getCurrency(), Reset)
			total = total + card.getValue(false)*float64(card.SerraCount) + card.getValue(true)*float64(card.SerraCountFoil)
		}
	}

	if !drawImg {
		fmt.Printf("\nTotal Value: %s%.2f%s%s\n", Yellow, total, getCurrency(), Reset)
	}
}

func showCardDetails(card *Card) error {
	if drawImg {
		drawImage(card)
	} else {
		fmt.Printf("%s%s%s (%s/%s)\n", Purple, card.Name, Reset, card.Set, card.CollectorNumber)
		fmt.Printf("Available: %d / %d\n", card.SerraCount + card.SerraCountFoil - card.SerraCountDeck - card.SerraCountFoilDeck, card.SerraCount + card.SerraCountFoil)
		fmt.Printf("Added: %s\n", stringToTime(card.SerraCreated))
		fmt.Printf("Rarity: %s\n", card.Rarity)
		fmt.Printf("Scryfall: %s\n", strings.Replace(card.ScryfallURI, "?utm_source=api", "", 1))

		fmt.Printf("\n%sCurrent Value%s\n", Green, Reset)
		fmt.Printf("* Normal: %dx %s%.2f%s%s\n", card.SerraCount, Yellow, card.getValue(false), getCurrency(), Reset)
		if card.SerraCountFoil > 0 {
			fmt.Printf("* Foil: %dx %s%.2f%s%s\n", card.SerraCountFoil, Yellow, card.getValue(true), getCurrency(), Reset)
		}

		fmt.Printf("\n%sValue History%s\n", Green, Reset)
		showPriceHistory(card.SerraPrices, "* ", false)

		fmt.Println()
	}
	return nil
}

func drawImage(card *Card) {
	fmt.Printf("%s - %s (%s/%s)\n", card.Name, card.SetName, card.Set, card.CollectorNumber)
	data, err := base64.StdEncoding.DecodeString(card.SerraImage64)
	if err == nil {
		reader := bytes.NewReader(data)
		img, err := png.Decode(reader)
		img = resize.Resize(305, 425, img, resize.Bicubic)
		if err == nil {
			kittyimg.Fprintln(os.Stdout, img)
		}
	}
	fmt.Println()
}
