package com.trunksbomb.minetriad.game;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public final class TriadMatch {
    private final MatchParticipant firstParticipant;
    private final MatchParticipant secondParticipant;
    private final TriadRuleSet ruleSet;
    private final List<GameCard> firstHand;
    private final List<GameCard> secondHand;
    private final PlacedCard[] board;
    private MatchParticipant activeParticipant;

    public TriadMatch(
            MatchParticipant firstParticipant,
            MatchParticipant secondParticipant,
            List<GameCard> firstHand,
            List<GameCard> secondHand,
            TriadRuleSet ruleSet) {
        this.firstParticipant = firstParticipant;
        this.secondParticipant = secondParticipant;
        this.ruleSet = ruleSet;
        this.firstHand = validateHand(firstHand);
        this.secondHand = validateHand(secondHand);
        this.board = new PlacedCard[BoardCell.SIZE * BoardCell.SIZE];
        this.activeParticipant = firstParticipant;
    }

    public MatchParticipant activeParticipant() {
        return activeParticipant;
    }

    public boolean isComplete() {
        return Arrays.stream(board).noneMatch(card -> card == null);
    }

    public int scoreFor(MatchParticipant participant) {
        int boardScore = (int) Arrays.stream(board)
                .filter(card -> card != null && card.owner().equals(participant))
                .count();
        int handScore = handFor(participant).size();
        return boardScore + handScore;
    }

    public List<GameCard> handFor(MatchParticipant participant) {
        if (participant.equals(firstParticipant)) {
            return List.copyOf(firstHand);
        }
        if (participant.equals(secondParticipant)) {
            return List.copyOf(secondHand);
        }
        throw new IllegalArgumentException("Participant is not part of this match");
    }

    public PlacedCard cardAt(BoardCell cell) {
        return board[cell.index()];
    }

    public MoveResult play(TriadMove move) {
        if (!activeParticipant.equals(move.participant())) {
            return MoveResult.failure("It is not that participant's turn");
        }
        if (cardAt(move.targetCell()) != null) {
            return MoveResult.failure("That board cell is already occupied");
        }

        List<GameCard> hand = mutableHandFor(move.participant());
        if (move.handIndex() < 0 || move.handIndex() >= hand.size()) {
            return MoveResult.failure("Hand index is out of bounds");
        }

        GameCard playedCard = hand.remove(move.handIndex());
        board[move.targetCell().index()] = new PlacedCard(move.participant(), playedCard);

        List<String> battleLog = new ArrayList<>();
        List<BoardCell> capturedCells = resolveCaptures(move.targetCell(), move.participant(), playedCard, battleLog);
        activeParticipant = otherParticipant(move.participant());
        return MoveResult.success(capturedCells, battleLog, playedCard.definition().name().getString(), playedCard.definition().id(), move.targetCell());
    }

    private List<BoardCell> resolveCaptures(BoardCell origin, MatchParticipant owner, GameCard playedCard, List<String> battleLog) {
        List<BoardCell> captured = new ArrayList<>();

        for (CardSide side : CardSide.values()) {
            BoardCell neighborCell = neighbor(origin, side);
            if (neighborCell == null) {
                continue;
            }

            PlacedCard neighbor = cardAt(neighborCell);
            if (neighbor == null || neighbor.owner().equals(owner)) {
                continue;
            }

            int attackValue = playedCard.value(side);
            int defendValue = neighbor.card().value(side.opposite());
            boolean capturedNeighbor = attackValue > defendValue;
            battleLog.add(String.format(
                    "%s %s=%d vs %s %s=%d at [%d,%d] -> %s",
                    playedCard.definition().name().getString(),
                    side.name(),
                    attackValue,
                    neighbor.card().definition().name().getString(),
                    side.opposite().name(),
                    defendValue,
                    neighborCell.row(),
                    neighborCell.column(),
                    capturedNeighbor ? "flip" : "hold"));
            if (capturedNeighbor) {
                board[neighborCell.index()] = new PlacedCard(owner, neighbor.card());
                captured.add(neighborCell);
            }
        }

        return captured;
    }

    private BoardCell neighbor(BoardCell cell, CardSide side) {
        int row = cell.row() + side.rowOffset();
        int column = cell.column() + side.columnOffset();
        if (row < 0 || row >= BoardCell.SIZE || column < 0 || column >= BoardCell.SIZE) {
            return null;
        }
        return new BoardCell(row, column);
    }

    private List<GameCard> mutableHandFor(MatchParticipant participant) {
        if (participant.equals(firstParticipant)) {
            return firstHand;
        }
        if (participant.equals(secondParticipant)) {
            return secondHand;
        }
        throw new IllegalArgumentException("Participant is not part of this match");
    }

    private MatchParticipant otherParticipant(MatchParticipant participant) {
        if (participant.equals(firstParticipant)) {
            return secondParticipant;
        }
        if (participant.equals(secondParticipant)) {
            return firstParticipant;
        }
        throw new IllegalArgumentException("Participant is not part of this match");
    }

    private static List<GameCard> validateHand(List<GameCard> hand) {
        if (hand.isEmpty()) {
            return new ArrayList<>();
        }
        if (hand.size() > 5) {
            throw new IllegalArgumentException("Triple Triad hands cannot contain more than 5 cards");
        }
        return new ArrayList<>(hand);
    }

    public TriadRuleSet ruleSet() {
        return ruleSet;
    }

    void forcePlace(BoardCell cell, PlacedCard placedCard) {
        board[cell.index()] = placedCard;
    }

    void forceActiveParticipant(MatchParticipant participant) {
        this.activeParticipant = participant;
    }
}
