from jsonschema import ValidationError


class TwineException(Exception):
    """All exceptions raised by the twine framework inherit from TwineException"""


class NotImplementedYet(TwineException):
    """Raised when you attempt to use a function whose high-level API is in place, but which is not implemented yet"""


class TwineValueException(TwineException, ValueError):
    """Raised when a python ValueError is appropriate to ensure all errors still also inherit from TwineException"""


class TwineTypeException(TwineException, TypeError):
    """Raised when a python TypeError is appropriate to ensure all errors still also inherit from TwineException"""


class TwineVersionConflict(TwineException):
    """Raised when the (optional) "twined_version" field in the twine file does not match the current installed version of twined"""


# --------------------- Exceptions relating to the twine itself ------------------------


class InvalidTwine(TwineException):
    """Raised when the specified twine is invalid for some reason"""


class InvalidTwineJson(InvalidTwine):
    """Raised when the JSON in the twine file is broken"""


class InvalidTwineContents(InvalidTwine, ValidationError):
    """Raised when the JSON in the twine file is not valid (eg doesn't match twine schema)"""


# --------------------- Exceptions relating to accessing/setting strands ------------------------


class UnknownStrand(TwineException, ValueError):
    """Raised when referencing a strand which is not defined in ALL_STRANDS"""


class StrandNotFound(TwineException, KeyError):
    """Raised when the attempting to access a strand not present in the twine"""


# --------------------- Exceptions relating to missing files/folders ------------------------


class FolderNotFound(TwineException):
    """Raised when a required folder (e.g. <data_dir>/input) cannot be found"""


class CredentialNotFound(TwineException):
    """Raised when a credential specified in the twine file is not present in either the environment or a .env file"""


class TwineFileNotFound(TwineException, FileNotFoundError):
    """Raised when the specified twine file is not present"""


class ConfigurationValuesFileNotFound(TwineException, FileNotFoundError):
    """Raised when attempting to read configuration values from a file that is missing"""


class ConfigurationManifestFileNotFound(TwineException, FileNotFoundError):
    """Raised when a configuration manifest file is required by a twine, but is not present in the input directory"""


class InputManifestFileNotFound(TwineException, FileNotFoundError):
    """Raised when an input manifest file is required by a twine, but is not present in the input directory"""


class InputValuesFileNotFound(TwineException, FileNotFoundError):
    """Raised when attempting to read input values from a file that is missing"""


class OutputManifestFileNotFound(TwineException, FileNotFoundError):
    """Raised when twined checks that output manifest file has been produced, but it is not present in the output directory"""


class OutputValuesFileNotFound(TwineException, FileNotFoundError):
    """Raised when attempting to read output values from a file that is missing"""


# --------------------- Exceptions relating to validation of JSON data (input, output, config values) ------------------


class InvalidSourceKindException(TwineException):
    """Raised when attempting to use the json loader for a disallowed kind"""


class InvalidValues(TwineException):
    """Raised when JSON data (like Config data, Input Values or Output Values) is invalid"""


class InvalidValuesJson(InvalidValues):
    """Raised when the JSON in the file or string is broken so cannot be parsed"""


class InvalidValuesContents(InvalidValues, ValidationError):
    """Raised when the JSON in the file is not valid according to its matching schema."""


# --------------------- Exceptions relating to validation of manifests ------------------------


class InvalidManifest(TwineException):
    """Raised when a manifest loaded from JSON does not pass validation"""


class InvalidManifestJson(InvalidManifest):
    """Raised when the json in the manifest file is broken"""


class InvalidManifestType(InvalidManifest):
    """Raised when user attempts to create a manifest of an invalid type"""


class InvalidManifestContents(InvalidManifest, ValidationError):
    """Raised when the manifest files are missing or do not match tags, sequences, clusters, extensions etc as required"""


# --------------------- Exceptions relating to access of data using the Twine instance ------------------------

# TODO This is related to filtering files from a manifest. Determine whether this belongs here,
#  or whether we should port the filtering code across from the SDK.
class UnexpectedNumberOfResults(TwineException):
    """Raise when searching for a single data file (or a particular number of data files) and the number of results exceeds that expected"""


# --------------------- Maps allowing customised exceptions per-strand (simplifies code elsewhere) ------------------


file_not_found_map = {
    "twine": TwineFileNotFound,
    "configuration_values": ConfigurationValuesFileNotFound,
    "input_values": InputValuesFileNotFound,
    "output_values": OutputValuesFileNotFound,
    "configuration_manifest": ConfigurationManifestFileNotFound,
    "input_manifest": InputManifestFileNotFound,
    "output_manifest": OutputManifestFileNotFound,
}

# TODO Specialised per-strand exceptions to help drill to the root of the issues
invalid_json_map = {
    "twine": InvalidTwineJson,
    "children": InvalidValuesJson,
    "configuration_values": InvalidValuesJson,
    "input_values": InvalidValuesJson,
    "output_values": InvalidValuesJson,
    "configuration_manifest": InvalidManifestJson,
    "input_manifest": InvalidManifestJson,
    "output_manifest": InvalidManifestJson,
}

# TODO Specialised per-strand exceptions to help drill to the root of the issues
invalid_contents_map = {
    "twine": InvalidTwineContents,
    "children": InvalidValuesContents,
    "configuration_values": InvalidValuesContents,
    "input_values": InvalidValuesContents,
    "output_values": InvalidValuesContents,
    "configuration_manifest": InvalidManifestContents,
    "input_manifest": InvalidManifestContents,
    "output_manifest": InvalidManifestContents,
}
