import glob
import hashlib
import logging
import os
from pathlib import Path
from subprocess import check_output, DEVNULL, CalledProcessError
from typing import Text, Optional, Union

from rasa.shared.constants import DEFAULT_MODELS_PATH

from rasa.exceptions import ModelNotFound


logger = logging.getLogger(__name__)

# TODO: rename this whole module.


def get_local_model(model_path: Text = DEFAULT_MODELS_PATH) -> Text:
    """Returns verified path to local model archive.

    Args:
        model_path: Path to the zipped model. If it's a directory, the latest
                    trained model is returned.

    Returns:
        Path to the zipped model. If it's a directory, the latest
                    trained model is returned.

    Raises:
        ModelNotFound Exception: When no model could be found at the provided path.

    """
    if not model_path:
        raise ModelNotFound("No path specified.")
    elif not os.path.exists(model_path):
        raise ModelNotFound(f"No file or directory at '{model_path}'.")

    if os.path.isdir(model_path):
        file_model_path = get_latest_model(model_path)
        if not file_model_path:
            raise ModelNotFound(
                f"Could not find any Rasa model files in '{model_path}'."
            )
        model_path = file_model_path
    elif not model_path.endswith(".tar.gz"):
        raise ModelNotFound(f"Path '{model_path}' does not point to a Rasa model file.")

    return model_path


def get_latest_model(model_path: Text = DEFAULT_MODELS_PATH) -> Optional[Text]:
    """Get the latest model from a path.

    Args:
        model_path: Path to a directory containing zipped models.

    Returns:
        Path to latest model in the given directory.

    """
    if not model_path:
        return None

    if not os.path.exists(model_path) or os.path.isfile(model_path):
        model_path = os.path.dirname(model_path)

    list_of_files = glob.glob(os.path.join(model_path, "*.tar.gz"))

    if len(list_of_files) == 0:
        return None

    return max(list_of_files, key=os.path.getmtime)


def get_model_for_finetuning(
    previous_model_file_or_dir: Union[Path, Text]
) -> Optional[Path]:
    """Gets validated path for model to finetune.

    Args:
        previous_model_file: Path to model file which should be used for finetuning or
            a directory in case the latest trained model should be used.

    Returns:
        Path to model archive. `None` if there is no model.
    """
    model_file: Optional[Union[Path, Text]] = previous_model_file_or_dir
    if Path(previous_model_file_or_dir).is_dir():
        logger.debug(
            f"Trying to load latest model from '{previous_model_file_or_dir}' for "
            f"finetuning."
        )
        model_file = get_latest_model(previous_model_file_or_dir)

    if model_file and Path(model_file).is_file():
        return Path(model_file)

    logger.debug(
        "No valid model for finetuning found as directory either "
        "contains no model or model file cannot be found."
    )
    return None


def project_fingerprint() -> Optional[Text]:
    """Create a hash for the project in the current working directory.

    Returns:
        project hash
    """
    try:
        remote = check_output(  # skipcq:BAN-B607,BAN-B603
            ["git", "remote", "get-url", "origin"], stderr=DEVNULL
        )
        return hashlib.sha256(remote).hexdigest()
    except (CalledProcessError, OSError):
        return None
