import re

from jinja2 import Environment, Undefined
from .constants import DEFAULT_OUTPUT_FILENAME_TEMPLATE
from .configuration_controller import ConfigurationController
from .logging_utils import get_ffx_logger
from .show_descriptor import ShowDescriptor


class EmptyStringUndefined(Undefined):
    def __str__(self):
        return ''


DIFF_ADDED_KEY = 'added'
DIFF_REMOVED_KEY = 'removed'
DIFF_CHANGED_KEY = 'changed'
DIFF_UNCHANGED_KEY = 'unchanged'

FILENAME_FILTER_TRANSLATION = str.maketrans(
    {
        "/": "-",
        ":": ";",
        "*": "",
        "'": "",
        "?": "#",
        "♥": "",
        "’": "",
    }
)
TMDB_FILLER_MARKERS = (" (*)", "(*)")
TMDB_EPISODE_RANGE_SUFFIX_REGEX = re.compile(r"\(([0-9]+)[-/]([0-9]+)\)$")
TMDB_EPISODE_PART_SUFFIX_REGEX = re.compile(r"\(([0-9]+)\)$")
RICH_COLOR_REGEX = re.compile(r"\[[a-z_]+\](.+)\[/[a-z_]+\]")


def dictDiff(a : dict, b : dict, ignoreKeys: list = [], removeKeys: list = []):
    """
    ignoreKeys: Ignored keys are filtered from calculating diff at all 
    removeKeys: Override diff calculation to remove keys certainly
    """

    a_filtered = {k:v for k,v in a.items() if not k in ignoreKeys}
    b_filtered = {k:v for k,v in b.items() if not k in ignoreKeys and k not in removeKeys}

    a_only = {k:v for k,v in a_filtered.items() if not k in b_filtered.keys()}
    b_only = {k:v for k,v in b_filtered.items() if not k in a_filtered.keys()}

    a_b = set(a_filtered.keys()) & set(b_filtered.keys())

    changed = {k:b_filtered[k] for k in a_b if a_filtered[k] != b_filtered[k]}
    unchanged = {k:b_filtered[k] for k in a_b if a_filtered[k] == b_filtered[k]}

    diffResult = {}


    if a_only:
        diffResult[DIFF_REMOVED_KEY] = a_only
        diffResult[DIFF_UNCHANGED_KEY] = unchanged
    if b_only:
        diffResult[DIFF_ADDED_KEY] = b_only
    if changed:
        diffResult[DIFF_CHANGED_KEY] = changed

    return diffResult


def dictKeysDiff(a : dict, b : dict):

    a_keys = set(a.keys())
    b_keys = set(b.keys())

    a_only = a_keys - b_keys
    b_only = b_keys - a_keys
    a_b = a_keys & b_keys

    changed = {k for k in a_b if a[k] != b[k]}

    diffResult = {}


    if a_only:
        diffResult[DIFF_REMOVED_KEY] = a_only
        diffResult[DIFF_UNCHANGED_KEY] = b_keys
    if b_only:
        diffResult[DIFF_ADDED_KEY] = b_only
    if changed:
        diffResult[DIFF_CHANGED_KEY] = changed

    return diffResult


def dictCache(element: dict, cache: list = []):
    for index in range(len(cache)):
        diff = dictKeysDiff(cache[index], element)
        if not diff:
            return index, cache
    cache.append(element)
    return -1, cache


def setDiff(a : set, b : set) -> set:

    a_only = a - b
    b_only = b - a
    a_and_b = a & b

    diffResult = {}

    if a_only:
        diffResult[DIFF_REMOVED_KEY] = a_only
        diffResult[DIFF_UNCHANGED_KEY] = a_and_b
    if b_only:
        diffResult[DIFF_ADDED_KEY] = b_only

    return diffResult


def permutateList(inputList: list, permutation: list):

    # 0,1,2: ABC
    # 0,2,1: ACB
    # 1,2,0: BCA

    pass



def filterFilename(fileName: str) -> str:
    """This filter replaces charactes from TMDB responses with characters
    less problemating when using in filenames or removes them"""

    return str(fileName).translate(FILENAME_FILTER_TRANSLATION).strip()

def substituteTmdbFilename(fileName: str) -> str:
    """If chaining this method with filterFilename use this one first as the latter will destroy some patterns"""

    normalizedFileName = str(fileName)

    for fillerMarker in TMDB_FILLER_MARKERS:
        normalizedFileName = normalizedFileName.replace(fillerMarker, '')

    episodeRangeMatch = TMDB_EPISODE_RANGE_SUFFIX_REGEX.search(normalizedFileName)
    if episodeRangeMatch is not None:
        partFirstIndex, partLastIndex = episodeRangeMatch.groups()
        return TMDB_EPISODE_RANGE_SUFFIX_REGEX.sub(
            f"Teil {partFirstIndex}-{partLastIndex}",
            normalizedFileName,
            count=1,
        )

    episodePartMatch = TMDB_EPISODE_PART_SUFFIX_REGEX.search(normalizedFileName)
    if episodePartMatch is not None:
        partIndex = episodePartMatch.group(1)
        return TMDB_EPISODE_PART_SUFFIX_REGEX.sub(
            f"Teil {partIndex}",
            normalizedFileName,
            count=1,
        )

    return normalizedFileName


def getEpisodeFileBasename(showName,
                            episodeName,
                            season,
                            episode,
                            indexSeasonDigits = None,
                            indexEpisodeDigits = None,
                            indicatorSeasonDigits = None,
                            indicatorEpisodeDigits = None,
                            context = None):
    """
    One Piece:
    indexSeasonDigits = 0,
    indexEpisodeDigits = 4,
    indicatorSeasonDigits = 2,
    indicatorEpisodeDigits = 4
    
    Three-Body:
    indexSeasonDigits = 0,
    indexEpisodeDigits = 2,
    indicatorSeasonDigits = 2,
    indicatorEpisodeDigits = 2
    
    Dragonball:
    indexSeasonDigits = 0,
    indexEpisodeDigits = 3,
    indicatorSeasonDigits = 2,
    indicatorEpisodeDigits = 3
    
    Boruto:
    indexSeasonDigits = 0,
    indexEpisodeDigits = 4,
    indicatorSeasonDigits = 2,
    indicatorEpisodeDigits = 4
    """

    cc: ConfigurationController = context['config'] if context is not None and 'config' in context.keys() else None
    configData = cc.getData() if cc is not None else {}
    outputFilenameTemplate = configData.get(ConfigurationController.OUTPUT_FILENAME_TEMPLATE_KEY,
                                            DEFAULT_OUTPUT_FILENAME_TEMPLATE)
    defaultDigitLengths = ShowDescriptor.getDefaultDigitLengths(context)

    if indexSeasonDigits is None:
        indexSeasonDigits = defaultDigitLengths[ShowDescriptor.INDEX_SEASON_DIGITS_KEY]
    if indexEpisodeDigits is None:
        indexEpisodeDigits = defaultDigitLengths[ShowDescriptor.INDEX_EPISODE_DIGITS_KEY]
    if indicatorSeasonDigits is None:
        indicatorSeasonDigits = defaultDigitLengths[ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY]
    if indicatorEpisodeDigits is None:
        indicatorEpisodeDigits = defaultDigitLengths[ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY]

    if context is not None and 'logger' in context.keys():
        logger = context['logger']
    else:
        logger = get_ffx_logger()


    indexSeparator = ' ' if indexSeasonDigits or indexEpisodeDigits else ''
    seasonIndex = '{num:{fill}{width}}'.format(num=season, fill='0', width=indexSeasonDigits) if indexSeasonDigits else ''
    episodeIndex = '{num:{fill}{width}}'.format(num=episode, fill='0', width=indexEpisodeDigits) if indexEpisodeDigits else ''

    indicatorSeparator = ' - ' if indicatorSeasonDigits or indicatorEpisodeDigits else ''
    seasonIndicator = 'S{num:{fill}{width}}'.format(num=season, fill='0', width=indicatorSeasonDigits) if indicatorSeasonDigits else ''
    episodeIndicator = 'E{num:{fill}{width}}'.format(num=episode, fill='0', width=indicatorEpisodeDigits) if indicatorEpisodeDigits else ''

    jinjaKwargs = {
        'ffx_show_name': showName,
        'ffx_index_separator': indexSeparator,
        'ffx_season_index': str(seasonIndex),
        'ffx_episode_index': str(episodeIndex),
        'ffx_index': str(seasonIndex) + str(episodeIndex),
        'ffx_episode_name': episodeName,
        'ffx_indicator_separator': indicatorSeparator,
        'ffx_season_indicator': str(seasonIndicator),
        'ffx_episode_indicator': str(episodeIndicator),
        'ffx_indicator': str(seasonIndicator) + str(episodeIndicator)
    }

    jinjaEnv = Environment(undefined=EmptyStringUndefined)
    jinjaTemplate = jinjaEnv.from_string(outputFilenameTemplate)
    return jinjaTemplate.render(**jinjaKwargs)

    # return ''.join(filenameTokens)


def formatRichColor(text: str, color: str = None):
    if color is None:
        return text
    else:
        return f"[{color}]{text}[/{color}]"

def removeRichColor(text: str):
    richColorMatch = RICH_COLOR_REGEX.search(str(text))
    if richColorMatch is None:
        return text
    else:
        return str(richColorMatch.group(1))
