"""
Algorithms for detecting play techniques
"""

from __future__ import annotations

import abc
from typing import Optional

__all__ = ["detect_play", "SimpleFinesse", "PlayTechniqueBase"]

from endplay.types import Card, Deal, Rank, SuitHolding


class NoMatch(Exception):
    pass


def rank_below(rank: Rank, holding: SuitHolding):
    "Return the rank just below `rank` in `holding`. Returns `None` if `rank` is the first card"
    prev = None
    for r in holding:
        if r == rank:
            return prev
        prev = r
    raise ValueError(f"{rank} not found in holding")


def rank_above(rank: Rank, holding: SuitHolding):
    "Return the rank just above `rank` in `holding`. Returns `None` if `rank` is the last card"
    foundRank = None
    for r in holding:
        if r == rank:
            foundRank = True
        elif foundRank:
            return r
    if foundRank:
        return None
    raise ValueError(f"{rank} not found in holding")


def rank_equivalent(rankA: Rank, rankB: Rank, remaining: SuitHolding):
    """
    Return true if `cardA` and `cardB` are equivalent cards, given that
    the only cards of that suit left in the deal are in `remaining`
    """
    foundA = False
    for rank in remaining:
        if rank == rankA:
            foundA = True
        elif foundA:
            return rank == rankB
    raise ValueError(f"{rankA} not found in remaining cards")


class PlayTechniqueBase(abc.ABC):
    def __init__(self):
        self.matches = False
        self.exception = None

    def __bool__(self):
        return self.matches

    @abc.abstractproperty
    def name(self) -> str:
        ...

    def match(self, deal: Deal, trick: list[Card]):
        try:
            if len(trick) != 4:
                raise ValueError("`trick` must contain four cards")
            for i, card in enumerate(trick):
                if not isinstance(card, Card):
                    trick[i] = Card(card)
            self._match(deal, trick)
        except NoMatch as e:
            self.exception = e
        return self.match

    @abc.abstractmethod
    def _match(self, deal: Deal, trick: list[Card]):
        ...


class Finesse(PlayTechniqueBase):
    def __init__(self):
        super().__init__()
        self.onside = False
        self.lho_covered = False
        self.rho_covered = False


class SimpleFinesse(PlayTechniqueBase):
    def __init__(self):
        super().__init__()
        self.is_onside = False
        self.lho_covered = False
        self.rho_covered = False

    @property
    def name(self):
        return "simple finesse"

    def _match(self, deal: Deal, trick: list[Card]):
        # First three cards must be the same suit
        suit = trick[0].suit
        if suit != trick[1].suit or suit != trick[2].suit:
            raise NoMatch("First three cards must be of the same suit")
        # Get all cards in the suit
        lho = deal[deal.first.lho][suit]
        rho = deal[deal.first.rho][suit]
        opps = lho.copy()
        opps.extend(rho)
        leader = deal[deal.first][suit]

        # Check for the following scenarios:
        # -

        leader.extend(deal[deal.first.partner][suit])
        total = opps.copy()
        total.extend(leader)
        # Find cards in the opponent's hands where the leader has the
        # card directly above/below


def detect_play(
    deal: Deal, play_history: list[Card], techniques: list[PlayTechniqueBase] = []
):
    if not techniques:
        techniques = [SimpleFinesse()]

    res = []
    for technique in techniques:
        if technique.match(deal, play_history):
            res.append(technique.name)
    return res
