import logging
import os
from dataclasses import dataclass
from pathlib import Path

from typing import Dict, List, Optional

from .training_plan import TrainingPlan

logger = logging.getLogger(__name__)

_REFERENCE_PLANS_DIR = Path(__file__).resolve().parent / "plans"


@dataclass(frozen=True)
class PlanInfo:
    plan_id: str
    name: str
    description: str


class PlanRepository:
    _plans: Dict[str, TrainingPlan] = {}

    _plan_infos: List[PlanInfo] = []

    _failed_plans: List[str] = []

    _include_reference_plans: bool = True

    _include_user_plans: bool = True

    @classmethod
    def get_plans(cls, refresh: bool = False, user_location: str = None) -> List[PlanInfo]:
        """
        Return all known plans.

        :param refresh: Forces an update of available plans from all repository sources.
        :param user_location: Can override user-defined plan location.  Generally not recommended for most use cases.
        """
        if refresh or len(cls._plans) == 0:
            cls._refresh_plans(user_location)

        return list(cls._plan_infos)

    def get_plan(self, plan_id: str, refresh_if_not_found: bool = True) -> Optional[TrainingPlan]:
        """
        Return plan by plan_id.  By default, this method update the list available plans from all repository sources if
        the requested plan is not found.

        :param plan_id: The plan id to get.
        :param refresh_if_not_found: Forces update of available plans from all repository sources if not found.
        """
        if plan_id not in self._plans and refresh_if_not_found:
            self._refresh_plans()

        return self._plans.get(plan_id)

    @classmethod
    def _refresh_plans(cls, user_location: str = None):
        cls._plans = {}
        cls._plan_infos = []
        cls._failed_plans = []

        if cls._include_reference_plans:
            cls._append_plan_paths(_get_reference_plan_files())

        if cls._include_user_plans:
            user_path = Path(user_location) if user_location is not None else None
            cls._append_plan_paths(_get_user_plan_paths(user_path))

    @classmethod
    def _append_plan_paths(cls, paths: List[Path]):
        for path in paths:
            try:
                plan = TrainingPlan.from_json_file(path)
                cls._plans[plan.plan_id] = plan
                cls._plan_infos.append(PlanInfo(plan.plan_id, plan.name, plan.description))
            except Exception as err:
                logger.error("Could not load plan %s: %s", path, err)
                cls._failed_plans.append(str(path))


def _get_reference_plan_files() -> List[Path]:
    return _generate_file_paths(_REFERENCE_PLANS_DIR)


def _get_user_plan_paths(location: Path = None) -> List[Path]:
    if location is None:
        location = Path.home().joinpath("Autotrainer").joinpath("training").joinpath("protocols")

    return _generate_file_paths(location)


def _generate_file_paths(location: Path) -> List[Path]:
    if Path(location).is_dir() and os.access(location, os.R_OK):
        try:
            return list(location.glob("*.json"))
        except FileNotFoundError:
            logger.warning(f"Training plans could not be read from {location}")

    return []
