package com.trunksbomb.minetriad.game;

import java.util.Comparator;
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import com.trunksbomb.minetriad.card.CardRegistry;

import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.phys.BlockHitResult;

public final class DuelSession {
    private enum Phase {
        PLAYING,
        PLAYER_ANIMATION,
        OPPONENT_ANIMATION,
        CLOSING_ANIMATION,
        OVERVIEW
    }

    private final MatchParticipant playerParticipant;
    private final MatchParticipant opponentParticipant;
    private final TriadMatch match;
    private final List<ResourceLocation> refundablePlayerCards;
    private final boolean refundCardsOnEnd;
    private final boolean aiVsAi;
    private final BlockPos tablePos;
    private Phase phase;

    public DuelSession(
            UUID playerId,
            String playerName,
            List<GameCard> playerHand,
            List<GameCard> opponentHand,
            List<ResourceLocation> refundablePlayerCards,
            boolean refundCardsOnEnd,
            boolean aiVsAi,
            BlockPos tablePos) {
        this.playerParticipant = new MatchParticipant(playerId, playerName);
        this.opponentParticipant = new MatchParticipant(UUID.nameUUIDFromBytes(("opponent:" + playerId).getBytes()), "Training Duelist");
        this.match = new TriadMatch(playerParticipant, opponentParticipant, playerHand, opponentHand, TriadRuleSet.CLASSIC_OPEN);
        this.refundablePlayerCards = List.copyOf(refundablePlayerCards);
        this.refundCardsOnEnd = refundCardsOnEnd;
        this.aiVsAi = aiVsAi;
        this.tablePos = tablePos.immutable();
        this.phase = Phase.PLAYING;
    }

    public Component startMessage() {
        return Component.literal("Duel started against Training Duelist. Right-click a space on the table with a card from your hotbar to play.");
    }

    public boolean isPlayerTurn() {
        return match.activeParticipant().equals(playerParticipant);
    }

    public boolean isComplete() {
        return match.isComplete();
    }

    public boolean isInOverview() {
        return phase == Phase.OVERVIEW;
    }

    public boolean isAnimating() {
        return phase == Phase.PLAYER_ANIMATION || phase == Phase.OPPONENT_ANIMATION;
    }

    public boolean isAwaitingPlayerAnimation() {
        return phase == Phase.PLAYER_ANIMATION;
    }

    public boolean isAwaitingOpponentAnimation() {
        return phase == Phase.OPPONENT_ANIMATION;
    }

    public void beginPlayerAnimation() {
        phase = Phase.PLAYER_ANIMATION;
    }

    public void beginOpponentAnimation() {
        phase = Phase.OPPONENT_ANIMATION;
    }

    public void returnToPlaying() {
        phase = Phase.PLAYING;
    }

    public void beginClosingAnimation() {
        phase = Phase.CLOSING_ANIMATION;
    }

    public boolean isClosingAnimation() {
        return phase == Phase.CLOSING_ANIMATION;
    }

    public boolean isAiVsAi() {
        return aiVsAi;
    }

    public void enterOverview() {
        phase = Phase.OVERVIEW;
    }

    public Component handSummary() {
        MutableComponent component = Component.literal("Your hand: ");
        List<GameCard> hand = match.handFor(playerParticipant);
        for (int index = 0; index < hand.size(); index++) {
            if (index > 0) {
                component.append(Component.literal(", "));
            }
            component.append(hand.get(index).definition().name());
        }
        return component;
    }

    public MoveResult playPlayerCard(ResourceLocation cardId, BlockHitResult hitResult, boolean allowFallbackSelection, net.minecraft.core.Direction tableFacing) {
        int handIndex = findHandIndex(cardId);
        if (handIndex < 0 && allowFallbackSelection && !match.handFor(playerParticipant).isEmpty()) {
            handIndex = 0;
        }
        if (handIndex < 0) {
            return MoveResult.failure("That selected card is not in your current duel hand");
        }

        BoardCell targetCell = BoardLocalSpace.cellForHit(hitResult, tableFacing);
        return match.play(new TriadMove(playerParticipant, handIndex, targetCell));
    }

    public BoardCell targetCell(BlockHitResult hitResult, net.minecraft.core.Direction tableFacing) {
        return BoardLocalSpace.cellForHit(hitResult, tableFacing);
    }

    public MoveResult playOpponentTurn() {
        return playAiTurnFor(opponentParticipant);
    }

    public MoveResult playAiTurn() {
        return playAiTurnFor(match.activeParticipant());
    }

    private MoveResult playAiTurnFor(MatchParticipant participant) {
        int bestHandIndex = -1;
        BoardCell bestCell = null;
        int bestCaptures = -1;

        List<GameCard> hand = match.handFor(participant);
        for (int handIndex = 0; handIndex < hand.size(); handIndex++) {
            for (BoardCell cell : openCells()) {
                TriadMatch probe = duplicateMatch();
                MoveResult result = probe.play(new TriadMove(participant, handIndex, cell));
                if (!result.valid()) {
                    continue;
                }
                int captures = result.capturedCells().size();
                if (captures > bestCaptures) {
                    bestCaptures = captures;
                    bestHandIndex = handIndex;
                    bestCell = cell;
                }
            }
        }

        if (bestHandIndex < 0 || bestCell == null) {
            return MoveResult.failure("Opponent could not find a legal move");
        }

        return match.play(new TriadMove(participant, bestHandIndex, bestCell));
    }

    public Component boardSummary() {
        MutableComponent component = Component.literal("Board: ");
        for (int row = 0; row < BoardCell.SIZE; row++) {
            if (row > 0) {
                component.append(Component.literal(" / "));
            }
            for (int column = 0; column < BoardCell.SIZE; column++) {
                if (column > 0) {
                    component.append(Component.literal(" "));
                }
                PlacedCard placedCard = match.cardAt(new BoardCell(row, column));
                if (placedCard == null) {
                    component.append(Component.literal("[ ]"));
                } else {
                    String ownerInitial = placedCard.owner().equals(playerParticipant) ? "P" : "O";
                    String cardInitial = placedCard.card().definition().name().getString().substring(0, 1).toUpperCase();
                    component.append(Component.literal("[" + ownerInitial + cardInitial + "]"));
                }
            }
        }
        return component;
    }

    public Component resultSummary() {
        int playerScore = match.scoreFor(playerParticipant);
        int opponentScore = match.scoreFor(opponentParticipant);
        if (playerScore > opponentScore) {
            return Component.literal("Duel complete. You win " + playerScore + " to " + opponentScore + ".");
        }
        if (opponentScore > playerScore) {
            return Component.literal("Duel complete. Training Duelist wins " + opponentScore + " to " + playerScore + ".");
        }
        return Component.literal("Duel complete. The match ends in a draw at " + playerScore + " to " + opponentScore + ".");
    }

    public Component overviewMessage() {
        return Component.literal("Duel overview. Right-click the table to finish and return cards.");
    }

    public List<ResourceLocation> refundablePlayerCards() {
        return refundablePlayerCards;
    }

    public List<ResourceLocation> playedPlayerCards() {
        Map<ResourceLocation, Integer> remainingCounts = new HashMap<>();
        for (ResourceLocation cardId : remainingPlayerCards()) {
            remainingCounts.merge(cardId, 1, Integer::sum);
        }

        List<ResourceLocation> playedCards = new ArrayList<>();
        for (ResourceLocation cardId : refundablePlayerCards) {
            int remaining = remainingCounts.getOrDefault(cardId, 0);
            if (remaining > 0) {
                remainingCounts.put(cardId, remaining - 1);
            } else {
                playedCards.add(cardId);
            }
        }
        return playedCards;
    }

    public List<ResourceLocation> remainingPlayerCards() {
        return match.handFor(playerParticipant).stream()
                .map(card -> card.definition().id())
                .toList();
    }

    public boolean refundCardsOnEnd() {
        return refundCardsOnEnd;
    }

    public boolean isAtTable(BlockPos blockPos) {
        return tablePos.equals(blockPos);
    }

    public UUID playerParticipantId() {
        return playerParticipant.id();
    }

    public UUID opponentParticipantId() {
        return opponentParticipant.id();
    }

    private int findHandIndex(ResourceLocation cardId) {
        List<GameCard> hand = match.handFor(playerParticipant);
        for (int index = 0; index < hand.size(); index++) {
            if (hand.get(index).definition().id().equals(cardId)) {
                return index;
            }
        }
        return -1;
    }

    private List<BoardCell> openCells() {
        return java.util.stream.IntStream.range(0, BoardCell.SIZE * BoardCell.SIZE)
                .mapToObj(index -> new BoardCell(index / BoardCell.SIZE, index % BoardCell.SIZE))
                .filter(cell -> match.cardAt(cell) == null)
                .toList();
    }

    private TriadMatch duplicateMatch() {
        TriadMatch duplicate = new TriadMatch(
                playerParticipant,
                opponentParticipant,
                match.handFor(playerParticipant),
                match.handFor(opponentParticipant),
                match.ruleSet());

        for (int row = 0; row < BoardCell.SIZE; row++) {
            for (int column = 0; column < BoardCell.SIZE; column++) {
                BoardCell cell = new BoardCell(row, column);
                PlacedCard placedCard = match.cardAt(cell);
                if (placedCard != null) {
                    duplicate.forcePlace(cell, placedCard);
                }
            }
        }
        duplicate.forceActiveParticipant(match.activeParticipant());
        return duplicate;
    }

}
