from os.path import dirname

from pyramid.settings import aslist

from h_assets._cached_file import CachedINIFile, CachedJSONFile


class Environment:
    """
    Environment for generating URLs for Hypothesis' static assets.

    Static assets are grouped into named bundles, defined in an ini-format
    config file. The relative URL that should be used when serving a file from
    a bundle is defined in a JSON manifest file, which is generated by the
    static asset build pipeline.

    Environment reads the set of bundles from the config file
    and the mapping between the file path and the output URL
    from a JSON manifest file and provides the ability to retrieve the final
    URLs for a bundle via the urls() method.
    """

    def __init__(
        self, assets_base_url, bundle_config_path, manifest_path, auto_reload=False
    ):
        """
        Construct an Environment from the given configuration files.

        :param assets_base_url: URL at which assets will be served,
            excluding the trailing slash.
        :param bundle_config_path: Asset bundles config file path.
        :param manifest_path: JSON file mapping bundle names to files and
            cache-busted URLs for them
        :param auto_reload: Automatically reload the config and manifest files
         if they change.
        """

        self.assets_base_url = assets_base_url

        self.manifest_file = CachedJSONFile(manifest_path, auto_reload=auto_reload)
        self.bundle_file = CachedINIFile(bundle_config_path, auto_reload=auto_reload)

    def files(self, bundle):
        """Return the file paths for all files in a bundle."""

        return aslist(self.bundle_file.load()["bundles"][bundle])

    def urls(self, bundle):
        """
        Return asset URLs for all files in a bundle.

        Returns the URLs at which all files in a bundle are served,
        read from the asset manifest.

        :param bundle: Bundle name to lookup.
        :return: A list of URLs with cache busting hashes
        """

        return [self.url(path) for path in self.files(bundle)]

    def url(self, path):
        """
        Return the cache-busted URL for an asset with a given path.

        :param path: Path to lookup
        :return: A URLs with cache busting hash
        """

        return f"{self.assets_base_url}/{self.manifest_file.load()[path]}"

    def check_cache_buster(self, path, query):
        """
        Check if the cache buster in an asset request URL matches the manifest.

        :param path: Path component of asset request
        :param query: Query string from asset request
        :return: Whether the asset path exists and the cache buster is valid
        """

        if path.startswith(self.assets_base_url):
            # Strip asset root (eg. `/assets/`) from path.
            path = path[len(self.assets_base_url) + 1 :]

        manifest = self.manifest_file.load()
        if path not in manifest:
            return False

        _, expected_query = manifest[path].split("?")
        return query == expected_query

    def asset_root(self):
        """Return the root directory from which assets are served."""

        return dirname(self.manifest_file.path)
