package services

import (
	"strings"

	dto "astrology-api/dto/tarot"
)

//////////////////////////////////////////////////////////////
// The Deck
//////////////////////////////////////////////////////////////

// The 78 cards are described here rather than fetched. VedicAstroAPI has no
// endpoint that lists them, its reading endpoints are metered, and the artwork
// lives in a public bucket at a URL derivable from the card id. Building the
// deck locally makes the catalogue pages free, instant and incapable of failing
// upstream — and it gives the reading endpoints something to validate a card
// name against, so a typo costs a 400 from us instead of a wasted API call and
// an opaque one from the vendor.
//
// Every id below was verified against the bucket; all 78 resolve.

const tarotImageBase = "https://s3.ap-south-1.amazonaws.com/images.vedicastroapi/tarot_images"

// Artwork styles the vendor publishes for each card.
var tarotImageStyles = []string{
	"classic",
	"artwork",
	"dark",
	"ghibli",
}

// Directions as the *reading* endpoints want them. Note "reverse", not
// "reversed": the shuffle endpoint says "reversed" and the readings reject it,
// which is why normaliseTarotDirection exists.
const (
	TarotUpright = "upright"

	TarotReverse = "reverse"
)

const (
	TarotArcanaMajor = "major"

	TarotArcanaMinor = "minor"
)

type tarotMajor struct {
	id string

	name string

	number int

	roman string
}

var tarotMajorArcana = []tarotMajor{
	{"the_fool", "The Fool", 0, "0"},
	{"the_magician", "The Magician", 1, "I"},
	{"the_high_priestess", "The High Priestess", 2, "II"},
	{"the_empress", "The Empress", 3, "III"},
	{"the_emperor", "The Emperor", 4, "IV"},
	{"the_hierophant", "The Hierophant", 5, "V"},
	{"the_lovers", "The Lovers", 6, "VI"},
	{"the_chariot", "The Chariot", 7, "VII"},
	{"strength", "Strength", 8, "VIII"},
	{"the_hermit", "The Hermit", 9, "IX"},
	{"wheel_of_fortune", "Wheel of Fortune", 10, "X"},
	{"justice", "Justice", 11, "XI"},
	{"the_hanged_man", "The Hanged Man", 12, "XII"},
	{"death", "Death", 13, "XIII"},
	{"temperance", "Temperance", 14, "XIV"},
	{"the_devil", "The Devil", 15, "XV"},
	{"the_tower", "The Tower", 16, "XVI"},
	{"the_star", "The Star", 17, "XVII"},
	{"the_moon", "The Moon", 18, "XVIII"},
	{"the_sun", "The Sun", 19, "XIX"},
	{"judgement", "Judgement", 20, "XX"},
	{"the_world", "The World", 21, "XXI"},
}

// Suits in the order the site lists them.
var tarotSuits = []string{
	"cups",
	"wands",
	"swords",
	"pentacles",
}

type tarotRank struct {
	id string

	name string

	number int

	court bool
}

var tarotRanks = []tarotRank{
	{"ace", "Ace", 1, false},
	{"two", "Two", 2, false},
	{"three", "Three", 3, false},
	{"four", "Four", 4, false},
	{"five", "Five", 5, false},
	{"six", "Six", 6, false},
	{"seven", "Seven", 7, false},
	{"eight", "Eight", 8, false},
	{"nine", "Nine", 9, false},
	{"ten", "Ten", 10, false},
	{"page", "Page", 11, true},
	{"knight", "Knight", 12, true},
	{"queen", "Queen", 13, true},
	{"king", "King", 14, true},
}

var (
	tarotDeck = buildTarotDeck()

	tarotByID = indexTarotDeck(tarotDeck)
)

// tarotImages builds the artwork URLs for one card id.
func tarotImages(id string) dto.TarotCardImages {

	images := dto.TarotCardImages{
		Upright:  make(map[string]string, len(tarotImageStyles)),
		Reversed: make(map[string]string, len(tarotImageStyles)),
		Back:     make(map[string]string, len(tarotImageStyles)),
	}

	for _, style := range tarotImageStyles {

		images.Upright[style] = tarotImageBase + "/" + style + "/" + id + "_upright.png"

		images.Reversed[style] = tarotImageBase + "/" + style + "/" + id + "_reverse.png"

		images.Back[style] = tarotImageBase + "/tarot_back/" + style + ".png"
	}

	return images
}

func buildTarotDeck() []dto.TarotCard {

	deck := make([]dto.TarotCard, 0, 78)

	for _, major := range tarotMajorArcana {

		deck = append(deck, dto.TarotCard{
			ID:     major.id,
			Name:   major.name,
			Arcana: TarotArcanaMajor,
			Number: major.number,
			Roman:  major.roman,
			Images: tarotImages(major.id),
		})
	}

	for _, suit := range tarotSuits {

		for _, rank := range tarotRanks {

			id := rank.id + "_of_" + suit

			deck = append(deck, dto.TarotCard{
				ID:     id,
				Name:   rank.name + " of " + titleCase(suit),
				Arcana: TarotArcanaMinor,
				Suit:   suit,
				Number: rank.number,
				Court:  rank.court,
				Images: tarotImages(id),
			})
		}
	}

	return deck
}

// titleCase capitalises each word. strings.Title is deprecated and pulling in
// golang.org/x/text for four suit names would not be a trade worth making.
func titleCase(value string) string {

	words := strings.Fields(strings.ReplaceAll(value, "_", " "))

	for index, word := range words {
		words[index] = strings.ToUpper(word[:1]) + strings.ToLower(word[1:])
	}

	return strings.Join(words, " ")
}

func indexTarotDeck(deck []dto.TarotCard) map[string]*dto.TarotCard {

	index := make(map[string]*dto.TarotCard, len(deck))

	for position := range deck {
		index[deck[position].ID] = &deck[position]
	}

	return index
}

//////////////////////////////////////////////////////////////
// Lookups
//////////////////////////////////////////////////////////////

// TarotCardByID finds a card by its vendor id. Names are matched leniently —
// "The Fool", "the fool" and "the-fool" all resolve — because the id travels
// through a web form and back.
func TarotCardByID(id string) (*dto.TarotCard, bool) {

	normalised := normaliseTarotID(id)

	if normalised == "" {
		return nil, false
	}

	card, found := tarotByID[normalised]

	return card, found
}

func normaliseTarotID(id string) string {

	id = strings.ToLower(strings.TrimSpace(id))

	id = strings.NewReplacer(" ", "_", "-", "_").Replace(id)

	for strings.Contains(id, "__") {
		id = strings.ReplaceAll(id, "__", "_")
	}

	return id
}

// normaliseTarotDirection maps whatever the client sends onto what the reading
// endpoints accept. This matters: the shuffle endpoint deals a card labelled
// "reversed", and passing that straight back to a reading endpoint returns a
// bare HTTP 400 with no explanation.
func normaliseTarotDirection(direction string) string {

	switch strings.ToLower(strings.TrimSpace(direction)) {

	case "reverse", "reversed", "rev", "down", "downward":
		return TarotReverse

	default:
		return TarotUpright
	}
}

// tarotLang defaults the language, which the Hindi pages of the site rely on.
func tarotLang(lang string) string {

	lang = strings.ToLower(strings.TrimSpace(lang))

	if lang == "" {
		return "en"
	}

	return lang
}

//////////////////////////////////////////////////////////////
// Catalogue
//////////////////////////////////////////////////////////////

// TarotCatalogue returns the deck, filtered. It never fails and never calls
// upstream.
func TarotCatalogue(request dto.TarotCardListRequest) *dto.TarotCardListResponse {

	arcana := strings.ToLower(strings.TrimSpace(request.Arcana))

	suit := strings.ToLower(strings.TrimSpace(request.Suit))

	search := strings.ToLower(strings.TrimSpace(request.Search))

	response := &dto.TarotCardListResponse{
		Cards: make([]dto.TarotCard, 0, len(tarotDeck)),
	}

	for _, card := range tarotDeck {

		if arcana != "" && card.Arcana != arcana {
			continue
		}

		if suit != "" && card.Suit != suit {
			continue
		}

		if search != "" && !strings.Contains(strings.ToLower(card.Name), search) {
			continue
		}

		if card.Arcana == TarotArcanaMajor {
			response.Major++
		} else {
			response.Minor++
		}

		response.Cards = append(response.Cards, card)
	}

	response.Total = len(response.Cards)

	return response
}

// drawnTarotCard resolves a card and a direction into the shape a spread
// returns, with the artwork for that direction already picked out.
func drawnTarotCard(card *dto.TarotCard, direction string, position string) dto.TarotDrawnCard {

	drawn := dto.TarotDrawnCard{
		Position:  position,
		ID:        card.ID,
		Name:      card.Name,
		Arcana:    card.Arcana,
		Suit:      card.Suit,
		Direction: direction,
		Back:      card.Images.Back,
	}

	if direction == TarotReverse {
		drawn.Image = card.Images.Reversed
	} else {
		drawn.Image = card.Images.Upright
	}

	return drawn
}
