import logging
import os

from mopidy import backend, exceptions, models
from mopidy.audio import scan, tags
from mopidy.internal import path

logger = logging.getLogger(__name__)


class FileLibraryProvider(backend.LibraryProvider):
    """Library for browsing local files."""

    # TODO: get_images that can pull from metadata and/or .folder.png etc?
    # TODO: handle playlists?

    @property
    def root_directory(self):
        if not self._media_dirs:
            return None
        elif len(self._media_dirs) == 1:
            uri = path.path_to_uri(self._media_dirs[0]["path"])
        else:
            uri = "file:root"
        return models.Ref.directory(name="Files", uri=uri)

    def __init__(self, backend, config):
        super().__init__(backend)
        self._media_dirs = list(self._get_media_dirs(config))
        self._show_dotfiles = config["file"]["show_dotfiles"]
        self._excluded_file_extensions = tuple(
            file_ext.lower()
            for file_ext in config["file"]["excluded_file_extensions"]
        )
        self._follow_symlinks = config["file"]["follow_symlinks"]

        self._scanner = scan.Scanner(timeout=config["file"]["metadata_timeout"])

    def browse(self, uri):
        logger.debug("Browsing files at: %s", uri)
        result = []
        local_path = path.uri_to_path(uri)

        if str(local_path) == "root":
            return list(self._get_media_dirs_refs())

        if not self._is_in_basedir(local_path):
            logger.warning(
                "Rejected attempt to browse path (%s) outside dirs defined "
                "in file/media_dirs config.",
                uri,
            )
            return []
        if path.uri_to_path(uri).is_file():
            logger.error("Rejected attempt to browse file (%s)", uri)
            return []

        for dir_entry in local_path.iterdir():
            child_path = dir_entry.resolve()
            uri = path.path_to_uri(child_path)

            if not self._show_dotfiles and dir_entry.name.startswith("."):
                continue

            if (
                self._excluded_file_extensions
                and dir_entry.suffix in self._excluded_file_extensions
            ):
                continue

            if child_path.is_symlink() and not self._follow_symlinks:
                logger.debug("Ignoring symlink: %s", uri)
                continue

            if not self._is_in_basedir(child_path):
                logger.debug("Ignoring symlink to outside base dir: %s", uri)
                continue

            if child_path.is_dir():
                result.append(
                    models.Ref.directory(name=dir_entry.name, uri=uri)
                )
            elif child_path.is_file():
                result.append(models.Ref.track(name=dir_entry.name, uri=uri))

        def order(item):
            return (item.type != models.Ref.DIRECTORY, item.name)

        result.sort(key=order)

        return result

    def lookup(self, uri):
        logger.debug("Looking up file URI: %s", uri)
        local_path = path.uri_to_path(uri)

        try:
            result = self._scanner.scan(uri)
            track = tags.convert_tags_to_track(result.tags).replace(
                uri=uri, length=result.duration
            )
        except exceptions.ScannerError as e:
            logger.warning("Failed looking up %s: %s", uri, e)
            track = models.Track(uri=uri)

        if not track.name:
            track = track.replace(name=local_path.name)

        return [track]

    def _get_media_dirs(self, config):
        for entry in config["file"]["media_dirs"]:
            media_dir = {}
            media_dir_split = entry.split("|", 1)
            local_path = path.expand_path(media_dir_split[0])

            if local_path is None:
                logger.debug(
                    "Failed expanding path (%s) from file/media_dirs config "
                    "value.",
                    media_dir_split[0],
                )
                continue
            elif not local_path.is_dir():
                logger.warning(
                    "%s is not a directory. Please create the directory or "
                    "update the file/media_dirs config value.",
                    local_path,
                )
                continue

            media_dir["path"] = local_path
            if len(media_dir_split) == 2:
                media_dir["name"] = media_dir_split[1]
            else:
                # TODO Mpd client should accept / in dir name
                media_dir["name"] = media_dir_split[0].replace(os.sep, "+")

            yield media_dir

    def _get_media_dirs_refs(self):
        for media_dir in self._media_dirs:
            yield models.Ref.directory(
                name=media_dir["name"], uri=path.path_to_uri(media_dir["path"])
            )

    def _is_in_basedir(self, local_path):
        return any(
            path.is_path_inside_base_dir(local_path, media_dir["path"])
            for media_dir in self._media_dirs
        )
