package engine

import (
	"errors"
	"fmt"
	"log"
	"math/rand"
	"sort"
	"sync"
	"time"
)

type Suit string

const (
	Hearts   Suit = "hearts"
	Diamonds Suit = "diamonds"
	Spades   Suit = "spades"
	Clubs    Suit = "clubs"
)

type Value string

const (
	Seven Value = "7"
	Eight Value = "8"
	Nine  Value = "9"
	Ten   Value = "10"
	Under Value = "under"
	Upper Value = "upper"
	King  Value = "king"
	Ace   Value = "ace"
)

type Card struct {
	Suit  Suit  `json:"suit"`
	Value Value `json:"value"`
}

type Player struct {
	ID   string `json:"id"`
	Name string `json:"name"`
	Hand []Card `json:"hand"`
}

type GameState string

const (
	Waiting GameState = "waiting"
	Playing GameState = "playing"
	Ended   GameState = "ended"
)

// ActiveRule describes a custom rule applied to this room.
type ActiveRule struct {
	Description string `json:"description"`
	ProposedBy  string `json:"proposed_by"`
}

type Game struct {
	ID             string          `json:"id"`
	Players        []*Player       `json:"players"`
	Deck           []Card          `json:"-"`
	DiscardPile    []Card          `json:"discard_pile"`
	CurrentPlayer  int             `json:"current_player_index"`
	ActiveSuit     Suit            `json:"active_suit"`
	PenaltyCards   int             `json:"penalty_cards"`
	SkipNext       bool            `json:"skip_next"`
	Direction      int             `json:"direction"` // 1 = clockwise, -1 = counter-clockwise
	TurnNumber     int             `json:"turn_number"`
	RoundNumber    int             `json:"round_number"`
	LastActionType string          `json:"last_action_type,omitempty"`
	LastActorIndex int             `json:"last_actor_index,omitempty"`
	State          GameState       `json:"state"`
	Winner         *Player         `json:"winner"`
	PendingChoice  *PendingChoice  `json:"pending_choice,omitempty"`
	ChoiceQueue    []PendingChoice `json:"choice_queue,omitempty"`
	ActiveRules    []ActiveRule    `json:"active_rules"`
	Mu             sync.RWMutex    `json:"-"`
	script         *ScriptRuntime
}

// PendingChoice is a UI prompt the current player must resolve before turn can continue.
type PendingChoice struct {
	PlayerID    string   `json:"player_id"`
	PlayerIndex int      `json:"player_index"`
	Prompt      string   `json:"prompt"`
	Options     []string `json:"options"`
}

func NewGame(id, scriptSrc string) (*Game, error) {
	if scriptSrc == "" {
		scriptSrc = DefaultScript
	}
	rt, err := NewScriptRuntime(scriptSrc)
	if err != nil {
		return nil, err
	}
	return &Game{
		ID:     id,
		State:  Waiting,
		Deck:   createDeck(),
		script: rt,
	}, nil
}

func (g *Game) ScriptSource() string {
	if g.script == nil {
		return DefaultScript
	}
	return g.script.Source()
}

func createDeck() []Card {
	suits := []Suit{Hearts, Diamonds, Spades, Clubs}
	values := []Value{Seven, Eight, Nine, Ten, Under, Upper, King, Ace}
	deck := make([]Card, 0, 32)
	for _, s := range suits {
		for _, v := range values {
			deck = append(deck, Card{Suit: s, Value: v})
		}
	}
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	r.Shuffle(len(deck), func(i, j int) { deck[i], deck[j] = deck[j], deck[i] })
	return deck
}

func (g *Game) AddPlayer(id, name string) error {
	g.Mu.Lock()
	defer g.Mu.Unlock()
	if g.State != Waiting {
		return errors.New("game already started")
	}
	if len(g.Players) >= 4 {
		return errors.New("game full")
	}
	for _, p := range g.Players {
		if p.ID == id {
			return nil
		}
	}
	g.Players = append(g.Players, &Player{ID: id, Name: name, Hand: []Card{}})
	return nil
}

func (g *Game) Start() error {
	g.Mu.Lock()
	defer g.Mu.Unlock()
	if len(g.Players) < 2 {
		return errors.New("not enough players")
	}
	if g.State == Playing {
		return errors.New("already started")
	}

	g.Deck = createDeck()
	for _, p := range g.Players {
		p.Hand = []Card{}
	}
	g.DiscardPile = nil
	g.PenaltyCards = 0
	g.SkipNext = false
	g.Direction = 1
	g.TurnNumber = 1
	g.RoundNumber = 1
	g.LastActionType = "start"
	g.LastActorIndex = 0
	g.Winner = nil
	g.PendingChoice = nil
	g.ChoiceQueue = nil

	for i := 0; i < 4; i++ {
		for _, p := range g.Players {
			c, err := g.drawCard()
			if err != nil {
				return err
			}
			p.Hand = append(p.Hand, c)
		}
	}

	firstCard, err := g.drawCard()
	if err != nil {
		return err
	}
	for firstCard.Value == Upper {
		g.Deck = append(g.Deck, firstCard)
		r := rand.New(rand.NewSource(time.Now().UnixNano()))
		r.Shuffle(len(g.Deck), func(i, j int) { g.Deck[i], g.Deck[j] = g.Deck[j], g.Deck[i] })
		firstCard, err = g.drawCard()
		if err != nil {
			return err
		}
	}
	g.DiscardPile = []Card{firstCard}
	g.ActiveSuit = firstCard.Suit
	g.State = Playing
	g.CurrentPlayer = 0

	state := g.buildState(g.Players[0], firstCard.Suit)
	res, err := g.script.OnPlayed([]Card{firstCard}, state)
	if err != nil {
		log.Printf("script onPlayed error on first card: %v", err)
	} else {
		g.PenaltyCards = res.PenaltyCards
		g.SkipNext = res.SkipNext
		g.ActiveSuit = res.ActiveSuit
	}

	return nil
}

func (g *Game) drawCard() (Card, error) {
	if len(g.Deck) == 0 && len(g.DiscardPile) <= 1 {
		return Card{}, errors.New("no cards available to draw")
	}
	if len(g.Deck) == 0 {
		top := g.DiscardPile[len(g.DiscardPile)-1]
		g.Deck = g.DiscardPile[:len(g.DiscardPile)-1]
		g.DiscardPile = []Card{top}
		r := rand.New(rand.NewSource(time.Now().UnixNano()))
		r.Shuffle(len(g.Deck), func(i, j int) { g.Deck[i], g.Deck[j] = g.Deck[j], g.Deck[i] })
	}
	if len(g.Deck) == 0 {
		return Card{}, errors.New("no cards available to draw")
	}
	card := g.Deck[len(g.Deck)-1]
	g.Deck = g.Deck[:len(g.Deck)-1]
	return card, nil
}

// PlayCards plays a single selected card from the current player's hand.
// chosenSuit is used when an Upper is played.
func (g *Game) PlayCards(playerID string, cardIndices []int, chosenSuit Suit) error {
	g.Mu.Lock()
	defer g.Mu.Unlock()

	if g.State != Playing {
		return errors.New("game not in playing state")
	}
	if len(cardIndices) == 0 {
		return errors.New("no cards selected")
	}
	if len(cardIndices) != 1 {
		return errors.New("only one card can be played per turn")
	}
	if g.PendingChoice != nil {
		return errors.New("resolve pending choice first")
	}

	p := g.Players[g.CurrentPlayer]
	if p.ID != playerID {
		return errors.New("not your turn")
	}
	actorIndex := g.CurrentPlayer

	// Validate all indices.
	seen := make(map[int]bool)
	for _, idx := range cardIndices {
		if idx < 0 || idx >= len(p.Hand) {
			return fmt.Errorf("invalid card index %d", idx)
		}
		if seen[idx] {
			return fmt.Errorf("duplicate card index %d", idx)
		}
		seen[idx] = true
	}

	// Sort descending so removal by index is safe.
	sorted := make([]int, len(cardIndices))
	copy(sorted, cardIndices)
	sort.Sort(sort.Reverse(sort.IntSlice(sorted)))

	// Collect the cards in the exact order selected by the player.
	// This allows scripts to reason about sequence-sensitive combos.
	cards := make([]Card, len(cardIndices))
	for i, idx := range cardIndices {
		cards[i] = p.Hand[idx]
	}

	top := g.DiscardPile[len(g.DiscardPile)-1]
	state := g.buildState(p, chosenSuit)

	// Ask script whether the play is legal.
	ok, err := g.script.CanPlay(cards, top, state)
	if err != nil {
		log.Printf("script canPlay error: %v", err)
		ok = false
	}
	if !ok {
		return errors.New("cannot play these cards")
	}

	// Remove cards from hand (highest index first to preserve lower indices).
	for _, idx := range sorted {
		p.Hand = append(p.Hand[:idx], p.Hand[idx+1:]...)
	}
	// Place cards on discard pile.
	for _, c := range cards {
		g.DiscardPile = append(g.DiscardPile, c)
	}

	// Apply side-effects.
	res, err := g.script.OnPlayed(cards, state)
	if err != nil {
		log.Printf("script onPlayed error: %v", err)
	} else {
		if err := g.applyScriptStateMutation(res); err != nil {
			return err
		}
		if res.InstantWin {
			g.State = Ended
			g.Winner = p
			return nil
		}
	}

	if len(p.Hand) == 0 {
		g.State = Ended
		g.Winner = p
		g.LastActionType = "win"
		g.LastActorIndex = actorIndex
		return nil
	}

	if res.ExtraTurn {
		// current player goes again — don't advance
		return nil
	}
	if g.PendingChoice != nil {
		return nil
	}
	g.LastActionType = "play"
	g.LastActorIndex = actorIndex
	g.nextTurn()
	g.LastActionType = "draw"
	g.LastActorIndex = actorIndex
	return nil
}

// PlayCard is a convenience wrapper for playing a single card.
func (g *Game) PlayCard(playerID string, cardIdx int, chosenSuit Suit) error {
	return g.PlayCards(playerID, []int{cardIdx}, chosenSuit)
}

func (g *Game) Draw(playerID string) error {
	g.Mu.Lock()
	defer g.Mu.Unlock()

	if g.State != Playing {
		return errors.New("game not in playing state")
	}
	if g.PendingChoice != nil {
		return errors.New("resolve pending choice first")
	}
	p := g.Players[g.CurrentPlayer]
	if p.ID != playerID {
		return errors.New("not your turn")
	}

	if g.PenaltyCards > 0 {
		for i := 0; i < g.PenaltyCards; i++ {
			c, err := g.drawCard()
			if err != nil {
				return err
			}
			p.Hand = append(p.Hand, c)
		}
		g.PenaltyCards = 0
	} else if g.SkipNext {
		g.SkipNext = false
	} else {
		c, err := g.drawCard()
		if err != nil {
			return err
		}
		p.Hand = append(p.Hand, c)
	}

	g.nextTurn()
	return nil
}

// ApplyChoice resolves a pending post-play choice for the current player.
func (g *Game) ApplyChoice(playerID, choice string) error {
	g.Mu.Lock()
	defer g.Mu.Unlock()

	if g.State != Playing {
		return errors.New("game not in playing state")
	}
	if g.PendingChoice == nil {
		return errors.New("no pending choice")
	}
	if g.PendingChoice.PlayerID != playerID {
		return errors.New("not your choice")
	}
	chooserIndex := g.PendingChoice.PlayerIndex

	valid := false
	for _, o := range g.PendingChoice.Options {
		if o == choice {
			valid = true
			break
		}
	}
	if !valid {
		return errors.New("invalid choice option")
	}

	p := g.Players[g.CurrentPlayer]
	state := g.buildState(p, g.ActiveSuit)
	res, err := g.script.OnChoice(choice, state)
	if err != nil {
		log.Printf("script onChoice error: %v", err)
	}
	if err := g.applyScriptStateMutation(res); err != nil {
		return err
	}

	if res.InstantWin {
		g.State = Ended
		g.Winner = p
		g.PendingChoice = nil
		g.ChoiceQueue = nil
		g.LastActionType = "win"
		g.LastActorIndex = chooserIndex
		return nil
	}
	if res.SetDiscardIdx {
		if res.DiscardIdx < 0 || res.DiscardIdx >= len(p.Hand) {
			return errors.New("invalid discard index")
		}
		extra := p.Hand[res.DiscardIdx]
		p.Hand = append(p.Hand[:res.DiscardIdx], p.Hand[res.DiscardIdx+1:]...)
		g.DiscardPile = append(g.DiscardPile, extra)

		extraState := g.buildState(p, g.ActiveSuit)
		extraRes, err := g.script.OnPlayed([]Card{extra}, extraState)
		if err != nil {
			log.Printf("script onPlayed error on extra discard: %v", err)
		} else {
			if err := g.applyScriptStateMutation(extraRes); err != nil {
				return err
			}
			if extraRes.InstantWin {
				g.State = Ended
				g.Winner = p
				g.PendingChoice = nil
				g.ChoiceQueue = nil
				g.LastActionType = "win"
				g.LastActorIndex = chooserIndex
				return nil
			}
			if len(p.Hand) == 0 {
				g.State = Ended
				g.Winner = p
				g.PendingChoice = nil
				return nil
			}
			if g.PendingChoice != nil {
				return nil
			}
			if extraRes.ExtraTurn {
				g.PendingChoice = nil
				return nil
			}
		}
	}
	if len(p.Hand) == 0 {
		g.State = Ended
		g.Winner = p
		g.PendingChoice = nil
		g.ChoiceQueue = nil
		g.LastActionType = "win"
		g.LastActorIndex = chooserIndex
		return nil
	}

	g.PendingChoice = nil
	if len(g.ChoiceQueue) > 0 {
		next := g.ChoiceQueue[0]
		g.ChoiceQueue = g.ChoiceQueue[1:]
		g.PendingChoice = &next
		g.LastActionType = "choice_pending"
		g.LastActorIndex = chooserIndex
		return nil
	}
	if res.ExtraTurn {
		g.LastActionType = "choice"
		g.LastActorIndex = chooserIndex
		return nil
	}
	g.LastActionType = "choice"
	g.LastActorIndex = chooserIndex
	g.nextTurn()
	return nil
}

func (g *Game) applyScriptStateMutation(res OnPlayedResult) error {
	g.PenaltyCards = res.PenaltyCards
	g.SkipNext = res.SkipNext
	if res.ActiveSuit != "" {
		g.ActiveSuit = res.ActiveSuit
	}
	if res.SetReverseDir {
		g.Direction = 1
		if res.ReverseDir {
			g.Direction = -1
		}
	}
	if res.SetPlayerHands {
		if len(res.PlayerHands) != len(g.Players) {
			return errors.New("playerHands length mismatch")
		}
		for i := range g.Players {
			h := make([]Card, len(res.PlayerHands[i]))
			copy(h, res.PlayerHands[i])
			g.Players[i].Hand = h
		}
	}
	if res.SetDiscardPile {
		if len(res.DiscardPile) == 0 {
			return errors.New("discard pile cannot be empty")
		}
		pile := make([]Card, len(res.DiscardPile))
		copy(pile, res.DiscardPile)
		g.DiscardPile = pile
	}
	if res.SetDeck {
		deck := make([]Card, len(res.Deck))
		copy(deck, res.Deck)
		g.Deck = deck
	}
	if res.SetCurrentPlayer {
		if res.CurrentPlayer < 0 || res.CurrentPlayer >= len(g.Players) {
			return errors.New("invalid currentPlayerIndex")
		}
		g.CurrentPlayer = res.CurrentPlayer
	}
	if res.ChoicePrompt != "" && len(res.ChoiceOptions) > 0 {
		target := g.CurrentPlayer
		if res.SetChoiceTargetPlayer {
			if res.ChoiceTargetPlayer < 0 || res.ChoiceTargetPlayer >= len(g.Players) {
				return errors.New("invalid choiceTargetPlayerIndex")
			}
			target = res.ChoiceTargetPlayer
		}
		ch := PendingChoice{
			PlayerID:    g.Players[target].ID,
			PlayerIndex: target,
			Prompt:      res.ChoicePrompt,
			Options:     append([]string(nil), res.ChoiceOptions...),
		}
		if g.PendingChoice == nil {
			g.PendingChoice = &ch
		} else {
			g.ChoiceQueue = append(g.ChoiceQueue, ch)
		}
	}
	return nil
}

func (g *Game) nextTurn() {
	n := len(g.Players)
	prev := g.CurrentPlayer
	g.CurrentPlayer = ((g.CurrentPlayer+g.Direction)%n + n) % n
	g.TurnNumber++
	if g.CurrentPlayer == 0 && prev != 0 {
		g.RoundNumber++
	}
}

// buildState constructs the GameScriptState for the current moment.
// chosenSuit is the suit the player selected (for Upper plays).
func (g *Game) buildState(p *Player, chosenSuit Suit) GameScriptState {
	handCopy := make([]Card, len(p.Hand))
	copy(handCopy, p.Hand)

	counts := make([]int, len(g.Players))
	hands := make([][]Card, len(g.Players))
	for i, pl := range g.Players {
		counts[i] = len(pl.Hand)
		h := make([]Card, len(pl.Hand))
		copy(h, pl.Hand)
		hands[i] = h
	}

	return GameScriptState{
		ActiveSuit:         g.ActiveSuit,
		PenaltyCards:       g.PenaltyCards,
		SkipNext:           g.SkipNext,
		ChosenSuit:         chosenSuit,
		Hand:               handCopy,
		PlayerHands:        hands,
		CurrentPlayerIndex: g.CurrentPlayer,
		PlayerCount:        len(g.Players),
		HandCounts:         counts,
		Direction:          g.Direction,
		TurnNumber:         g.TurnNumber,
		RoundNumber:        g.RoundNumber,
		LastActionType:     g.LastActionType,
		LastActorIndex:     g.LastActorIndex,
		PendingChoices: func() []ScriptPendingChoice {
			out := make([]ScriptPendingChoice, 0, 1+len(g.ChoiceQueue))
			if g.PendingChoice != nil {
				out = append(out, ScriptPendingChoice{
					TargetPlayerIndex: g.PendingChoice.PlayerIndex,
					Prompt:            g.PendingChoice.Prompt,
					Options:           append([]string(nil), g.PendingChoice.Options...),
				})
			}
			for _, q := range g.ChoiceQueue {
				out = append(out, ScriptPendingChoice{
					TargetPlayerIndex: q.PlayerIndex,
					Prompt:            q.Prompt,
					Options:           append([]string(nil), q.Options...),
				})
			}
			return out
		}(),
	}
}
