"""Card and set index container."""

import collections
import string
from typing import Dict
from typing import Iterable
from typing import List
from typing import Optional
from typing import Set
from typing import Tuple
from uuid import UUID

from mtg_ssm.containers.bundles import ScryfallDataSet
from mtg_ssm.mtg import util
from mtg_ssm.scryfall.models import ScryCard
from mtg_ssm.scryfall.models import ScrySet


def name_card_sort_key(card: ScryCard) -> Tuple[str, int, str]:
    """Key function for sorting cards in a by-name list."""
    card_num, card_var = util.collector_int_var(card)
    return (card.set, card_num or 0, card_var or "")  # TODO: sort by set release date


def set_card_sort_key(card: ScryCard) -> Tuple[int, str]:
    """Key function for sorting cards in a by-set list."""
    card_num, card_var = util.collector_int_var(card)
    return (card_num or 0, card_var or "")


def build_snnmas(
    card: ScryCard,
) -> Iterable[Tuple[Optional[str], str, Optional[str], Optional[int], Optional[str]]]:
    """Build set, name, number, multiverse id tuple keys."""
    names_cnums: Set[Tuple[str, Optional[str]]] = {(card.name, card.collector_number)}
    for i, card_face in enumerate(card.card_faces or ()):
        names_cnums |= {
            (card_face.name, card.collector_number),
            (card_face.name, card.collector_number + string.ascii_lowercase[i]),
        }
    names_cnums |= {(n, None) for n, _ in names_cnums}

    sets: Set[Optional[str]] = {card.set, None}

    mvids: Set[Optional[int]] = {None} | set(card.multiverse_ids or ())

    artists: Set[Optional[str]] = {card.artist, None}

    for name, number in names_cnums:
        for set_ in sets:
            for mvid in mvids:
                for artist in artists:
                    yield (set_, name, number, mvid, artist)


class ScryfallDataIndex:
    """Card and set indexes for scryfall data."""

    def __init__(self) -> None:
        self.id_to_card: Dict[UUID, ScryCard] = {}
        self.name_to_cards: Dict[str, List[ScryCard]] = {}
        self.setcode_to_cards: Dict[str, List[ScryCard]] = {}
        self.id_to_setindex: Dict[UUID, int] = {}
        self.setcode_to_set: Dict[str, ScrySet] = {}
        # snnma = Set, Name, (Collector) Number, Multiverse ID, Artist
        # TODO: convert to intersecting bitmap indexes
        # TODO: do we really need artist?
        self.snnma_to_id: Dict[
            Tuple[Optional[str], str, Optional[str], Optional[int], Optional[str]],
            Set[UUID],
        ] = {}

    def load_data(self, scrydata: ScryfallDataSet) -> None:
        """Load all cards and sets from a Scryfall data set."""
        self.id_to_card = {}
        self.id_to_setindex = {}
        self.setcode_to_set = {}

        self.snnma_to_id = collections.defaultdict(set)

        name_to_unsorted_cards: Dict[str, List[ScryCard]] = collections.defaultdict(
            list
        )
        setcode_to_unsorted_cards: Dict[str, List[ScryCard]] = {}

        for set_ in scrydata.sets:
            self.setcode_to_set[set_.code] = set_
            setcode_to_unsorted_cards[set_.code] = []

        for card in scrydata.cards:
            self.id_to_card[card.id] = card
            name_to_unsorted_cards[card.name].append(card)
            setcode_to_unsorted_cards[card.set].append(card)
            if not self.setcode_to_set[card.set].digital:
                for snnma in build_snnmas(card):
                    self.snnma_to_id[snnma].add(card.id)
        self.snnma_to_id = dict(self.snnma_to_id)

        for cards_list in name_to_unsorted_cards.values():
            cards_list.sort(key=name_card_sort_key)
        self.name_to_cards = dict(name_to_unsorted_cards)

        for cards_list in setcode_to_unsorted_cards.values():
            cards_list.sort(key=set_card_sort_key)
            self.id_to_setindex.update({c.id: i for i, c in enumerate(cards_list)})
        self.setcode_to_cards = dict(setcode_to_unsorted_cards)


class Oracle:
    """Container for an indexed Scryfall data set."""

    def __init__(self, scrydata: ScryfallDataSet) -> None:
        self._scrydata = scrydata
        self.cards = scrydata.cards
        self.sets = scrydata.sets
        self.index = ScryfallDataIndex()
        self.index.load_data(scrydata)
