"""Jsonnet functionalities."""

import json
import yaml
from argparse import Action, Namespace

from .optionals import _import_jsonschema, _import_jsonnet, get_config_read_mode
from .util import (ParserError, namespace_to_dict, dict_to_namespace, Path, _check_unknown_kwargs,
                   _get_key_value)
from .actions import _is_action_value_list
from .jsonschema import ActionJsonSchema


class ActionJsonnetExtVars(ActionJsonSchema):
    """Action to be used for jsonnet ext_vars."""

    def __init__(self, **kwargs):
        """Initializer for ActionJsonnetExtVars instance."""
        super().__init__(schema={'type': 'object'}, with_meta=False)


class ActionJsonnet(Action):
    """Action to parse a jsonnet, optionally validating against a jsonschema."""

    def __init__(self, **kwargs):
        """Initializer for ActionJsonnet instance.

        Args:
            ext_vars (str or None): Key where to find the external variables required to parse the jsonnet.
            schema (str or object or None): Schema to validate values against. Keyword argument required even if schema=None.

        Raises:
            ValueError: If a parameter is invalid.
            jsonschema.exceptions.SchemaError: If the schema is invalid.
        """
        if 'ext_vars' in kwargs or 'schema' in kwargs:
            _import_jsonnet('ActionJsonnet')
            _check_unknown_kwargs(kwargs, {'schema', 'ext_vars'})
            if 'ext_vars' in kwargs and not isinstance(kwargs['ext_vars'], (str, type(None))):
                raise ValueError('ext_vars has to be either None or a string.')
            self._ext_vars = kwargs.get('ext_vars', None)
            schema = kwargs.get('schema', None)
            if schema is not None:
                jsonvalidator = _import_jsonschema('ActionJsonnet')[1]
                if isinstance(schema, str):
                    try:
                        schema = yaml.safe_load(schema)
                    except Exception as ex:
                        raise ValueError('Problems parsing schema :: '+str(ex))
                jsonvalidator.check_schema(schema)
                self._validator = ActionJsonSchema._extend_jsonvalidator_with_default(jsonvalidator)(schema)
            else:
                self._validator = None
        elif '_ext_vars' not in kwargs or '_validator' not in kwargs:
            raise ValueError('Expected ext_vars and/or schema keyword arguments.')
        else:
            self._ext_vars = kwargs.pop('_ext_vars')
            self._validator = kwargs.pop('_validator')
            kwargs['type'] = str
            super().__init__(**kwargs)


    def __call__(self, *args, **kwargs):
        """Parses an argument as jsonnet using ext_vars if defined.

        Raises:
            TypeError: If the argument is not valid.
        """
        if len(args) == 0:
            kwargs['_ext_vars'] = self._ext_vars
            kwargs['_validator'] = self._validator
            if 'help' in kwargs and '%s' in kwargs['help'] and self._validator is not None:
                kwargs['help'] = kwargs['help'] % json.dumps(self._validator.schema, sort_keys=True)
            return ActionJsonnet(**kwargs)
        setattr(args[1], self.dest, self._check_type(args[2], cfg=args[1]))


    def _check_type(self, value, cfg):
        jsonschema = _import_jsonschema('ActionJsonnet')[0]
        islist = _is_action_value_list(self)
        ext_vars = {}
        if self._ext_vars is not None:
            try:
                ext_vars = _get_key_value(cfg, self._ext_vars)
            except:
                pass
        if not islist:
            value = [value]
        elif not isinstance(value, list):
            raise TypeError('For ActionJsonnet with nargs='+str(self.nargs)+' expected value to be list, received: value='+str(value)+'.')
        for num, val in enumerate(value):
            try:
                if isinstance(val, str):
                    val = self.parse(val, ext_vars=ext_vars, with_meta=True)
                elif self._validator is not None:
                    if isinstance(val, Namespace):
                        self._validator.validate(namespace_to_dict(val))
                    else:
                        self._validator.validate(val)
                value[num] = val
            except (TypeError, RuntimeError, yaml.parser.ParserError, jsonschema.exceptions.ValidationError) as ex:
                elem = '' if not islist else ' element '+str(num+1)
                raise TypeError('Parser key "'+self.dest+'"'+elem+': '+str(ex))
        return value if islist else value[0]


    @staticmethod
    def split_ext_vars(ext_vars):
        """Splits an ext_vars dict into the ext_codes and ext_vars required by jsonnet.

        Args:
            ext_vars (dict): External variables. Values can be strings or any other basic type.
        """
        if isinstance(ext_vars, Namespace):
            ext_vars = namespace_to_dict(ext_vars)
        ext_codes = {k: json.dumps(v) for k, v in ext_vars.items() if not isinstance(v, str)}
        ext_vars = {k: v for k, v in ext_vars.items() if isinstance(v, str)}
        return ext_vars, ext_codes


    def parse(self, jsonnet, ext_vars={}, with_meta=False):
        """Method that can be used to parse jsonnet independent from an ArgumentParser.

        Args:
            jsonnet (str): Either a path to a jsonnet file or the jsonnet content.
            ext_vars (dict): External variables. Values can be strings or any other basic type.

        Returns:
            argparse.Namespace: The parsed jsonnet object.

        Raises:
            TypeError: If the input is neither a path to an existent file nor a jsonnet.
        """
        _jsonnet = _import_jsonnet('ActionJsonnet')
        ext_vars, ext_codes = self.split_ext_vars(ext_vars)
        fpath = None
        fname = 'snippet'
        snippet = jsonnet
        try:
            fpath = Path(jsonnet, mode=get_config_read_mode())
        except:
            pass
        else:
            fname = jsonnet(absolute=False) if isinstance(jsonnet, Path) else jsonnet
            snippet = fpath.get_content()
        try:
            values = yaml.safe_load(_jsonnet.evaluate_snippet(fname, snippet, ext_vars=ext_vars, ext_codes=ext_codes))
        except Exception as ex:
            raise ParserError('Problems evaluating jsonnet "'+fname+'" :: '+str(ex))
        if self._validator is not None:
            self._validator.validate(values)
        if with_meta and isinstance(values, dict) and fpath is not None:
            values['__path__'] = fpath
        return dict_to_namespace(values)
