"""
This module manages loading/etc of Galaxy interactive tours.
"""
import logging
import os
from typing import (
    List,
    Union,
)

import yaml
from pydantic import parse_obj_as

from galaxy.exceptions import ObjectNotFound
from galaxy.navigation.data import load_root_component
from galaxy.util import config_directories_from_setting
from ._interface import ToursRegistry
from ._schema import TourList

log = logging.getLogger(__name__)

TOUR_EXTENSIONS = (".yml", ".yaml")

ROOT_COMPONENT = load_root_component()


def build_tours_registry(tour_directories: str):
    return ToursRegistryImpl(tour_directories)


def noop_warn(str):
    pass


def METHOD_NAME(contents_dict, warn=None, resolve_components=True):
    warn = warn or noop_warn
    #  Some of this can be done on the clientside.  Maybe even should?
    title_default = contents_dict.get("title_default")
    if "requirements" not in contents_dict:
        contents_dict["requirements"] = []

    for step in contents_dict["steps"]:
        # Remove attributes no longer used, so they are attempted to be
        # validated.
        if "backdrop" in step:
            warn(f"Deprecated and dropped property backdrop found in step {step}")
            step.pop("backdrop")

        if "component" in step and resolve_components:
            component = step.pop("component")
            step["element"] = ROOT_COMPONENT.resolve_component_locator(component).locator

        if "intro" in step:
            step["content"] = step.pop("intro")
        if "position" in step:
            step["placement"] = step.pop("position")
        if "element" not in step:
            step["orphan"] = True
        if title_default and "title" not in step:
            step["title"] = title_default


def get_tour_id_from_path(tour_path: Union[str, os.PathLike]) -> str:
    filename = os.path.basename(tour_path)
    return os.path.splitext(filename)[0]


def load_tour_from_path(tour_path: Union[str, os.PathLike], warn=None, resolve_components=True) -> dict:
    with open(tour_path) as f:
        tour = yaml.safe_load(f)
        METHOD_NAME(tour, warn=warn, resolve_components=resolve_components)
    return tour


def is_yaml(filename: str) -> bool:
    for ext in TOUR_EXTENSIONS:
        if filename.endswith(ext):
            return True
    return False


def tour_paths(target_path: Union[str, os.PathLike]) -> List[str]:
    paths = []
    if os.path.isdir(target_path):
        for filename in os.listdir(target_path):
            if is_yaml(filename):
                paths.append(str(os.path.join(target_path, filename)))
    else:
        paths.append(str(target_path))
    return paths


@ToursRegistry.register
class ToursRegistryImpl:
    def __init__(self, tour_directories):
        self.tour_directories = config_directories_from_setting(tour_directories)
        self._load_tours()

    def get_tours(self):
        """Return list of tours."""
        tours = []
        for k in self.tours.keys():
            tourdata = {
                "id": k,
                "name": self.tours[k].get("name"),
                "description": self.tours[k].get("description"),
                "tags": self.tours[k].get("tags"),
                "requirements": self.tours[k].get("requirements"),
            }
            tours.append(tourdata)
        return parse_obj_as(TourList, tours)

    def tour_contents(self, tour_id):
        """Return tour contents."""
        # Extra format translation could happen here (like the previous intro_to_tour)
        # For now just return the loaded contents.
        if tour_id not in self.tours:
            raise ObjectNotFound(f"tour {tour_id} not found")
        return self.tours.get(tour_id)

    def load_tour(self, tour_id):
        """Reload tour and return its contents."""
        tour_path = self._get_path_from_tour_id(tour_id)
        self._load_tour_from_path(tour_path)
        return self.tours.get(tour_id)

    def reload_tour(self, path):
        """Reload tour."""
        # We may safely assume that the path is within the tour directory
        filename = os.path.basename(path)
        if is_yaml(filename):
            self._load_tour_from_path(path)

    def _load_tours(self):
        self.tours = {}
        for tour_dir in self.tour_directories:
            for tour_path in tour_paths(tour_dir):
                self._load_tour_from_path(tour_path)

    def _load_tour_from_path(self, tour_path):
        tour_id = get_tour_id_from_path(tour_path)
        try:
            tour = load_tour_from_path(tour_path)
        except OSError:
            log.exception(f"Tour '{tour_id}' could not be loaded, error reading file.")
        except yaml.error.YAMLError:
            log.exception(f"Tour '{tour_id}' could not be loaded, error within file. Please check your YAML syntax.")
        except TypeError:
            log.exception(
                f"Tour '{tour_id}' could not be loaded, error within file."
                " Possibly spacing related. Please check your YAML syntax."
            )
        self.tours[tour_id] = tour
        log.info(f"Loaded tour '{tour_id}'")

    def _get_path_from_tour_id(self, tour_id):
        for tour_dir in self.tour_directories:
            for ext in TOUR_EXTENSIONS:
                tour_path = os.path.join(tour_dir, tour_id + ext)
                if os.path.exists(tour_path):
                    return tour_path