import itertools

from collections import OrderedDict
from os.path import basename
from typing import Dict, List, Optional, Sequence, Set

from ..manifest.item import ManifestItem, URLManifestItem
from ..manifest.sourcefile import SourceFile
from ..metadata.webfeatures.schema import FeatureEntry, FeatureFile, FileMatchingMode, WebFeaturesFile


class WebFeaturesMap:
    """
    Stores a mapping of web-features to their associated test paths.
    """

    def __init__(self) -> None:
        """
        Initializes the WebFeaturesMap with an OrderedDict to maintain feature order.
        """
        self._feature_tests_map_: OrderedDict[str, Set[str]] = OrderedDict()


    def add(self, feature: str, manifest_items: List[ManifestItem]) -> None:
        """
        Adds a web feature and its associated test paths to the map.

        Args:
            feature: The web-features identifier.
            manifest_items: The ManifestItem objects representing the test paths.
        """
        tests = self._feature_tests_map_.get(feature, set())
        self._feature_tests_map_[feature] = tests.union([
            manifest_item.url for manifest_item in manifest_items if isinstance(manifest_item, URLManifestItem)])


    def to_dict(self) -> Dict[str, List[str]]:
        """
        Returns:
            The plain dictionary representation of the map.
        """
        rv: Dict[str, List[str]] = {}
        for feature, manifest_items in self._feature_tests_map_.items():
            # Sort the list to keep output stable
            rv[feature] = sorted(manifest_items)
        return rv


class WebFeatureToTestsDirMapper:
    """
    Maps web-features to tests within a specified directory.
    """

    def __init__(
            self,
            all_test_files_in_dir: List[SourceFile],
            web_feature_file: Optional[WebFeaturesFile]):
        """
        Initializes the mapper with test paths and web feature information.
        """

        self.all_test_files_in_dir = all_test_files_in_dir
        self.test_path_to_manifest_items_map = dict([(basename(f.path), f.manifest_items()[1]) for f in self.all_test_files_in_dir])
        # Used to check if the current directory has a WEB_FEATURE_FILENAME
        self.web_feature_file = web_feature_file
        # Gets the manifest items for each test path and returns them into a single list.
        self. get_all_manifest_items_for_dir = list(itertools.chain.from_iterable([
            items for _, items in self.test_path_to_manifest_items_map.items()]))


    def _process_inherited_features(
            self,
            inherited_features: List[str],
            result: WebFeaturesMap) -> None:
        # No WEB_FEATURE.yml in this directory. Simply add the current features to the inherited features
        for inherited_feature in inherited_features:
            result.add(inherited_feature, self.get_all_manifest_items_for_dir)

    def _process_recursive_feature(
            self,
            inherited_features: List[str],
            feature: FeatureEntry,
            result: WebFeaturesMap) -> None:
        inherited_features.append(feature.name)
        result.add(feature.name, self.get_all_manifest_items_for_dir)

    def _process_non_recursive_feature(
            self,
            feature_name: str,
            files: Sequence[FeatureFile],
            result: WebFeaturesMap) -> None:
        # If the feature does not apply recursively, look at the individual
        # files and match them against all_test_files_in_dir.
        final_test_file_paths: List[ManifestItem] = []
        test_file_paths: Set[str] = set()
        excluded_test_file_paths: Set[str] = set()
        base_test_file_names = [basename(f.path) for f in self.all_test_files_in_dir]
        for test_file in files:
            matched_base_file_names = test_file.match_files(base_test_file_names)
            if test_file.matching_mode == FileMatchingMode.INCLUDE:
                test_file_paths.update(matched_base_file_names)
            elif test_file.matching_mode == FileMatchingMode.EXCLUDE:
                excluded_test_file_paths.update(matched_base_file_names)
        final_test_file_paths_set = test_file_paths - excluded_test_file_paths
        final_test_file_paths.extend(itertools.chain.from_iterable([
            self.test_path_to_manifest_items_map[f] for f in final_test_file_paths_set]))

        result.add(feature_name, final_test_file_paths)

    def run(self, result: WebFeaturesMap, inherited_features: List[str]) -> None:
        if self.web_feature_file:
            # Do not copy the inherited features because the presence of a
            # WEB_FEATURES.yml file indicates new instructions.
            inherited_features.clear()

            # Iterate over all the features in this new file
            for feature in self.web_feature_file.features:
                # Handle the "**" case
                if feature.does_feature_apply_recursively():
                    self._process_recursive_feature(inherited_features, feature, result)

                # Handle the non recursive case.
                elif isinstance(feature.files, List) and feature.files:
                    self._process_non_recursive_feature(feature.name, feature.files, result)
        else:
            self._process_inherited_features(inherited_features, result)
