from parallels.core.logging import get_logger
from parallels.core.registry import Registry
from parallels.core.utils.json_utils import encode_json, decode_json

logger = get_logger(__name__)


class Tracer(object):
    """
    :type _traces: list
    :type _meta: parallels.core.utils.tracer.TracerMeta
    """
    def __init__(self, session_file):
        """
        :type session_file: parallels.core.session_files.SessionFile
        """
        self._session_file = session_file
        self._traces = []

        def get_meta_data():
            if session_file.is_exists():
                try:
                    data = decode_json(session_file.read())
                    if isinstance(data, dict):
                        return data.get('meta')
                except Exception:
                    # traces could not be retrieved for some reason,
                    # just put exception into debug log
                    logger.exception()
            return None

        self._meta = self._init_meta(get_meta_data())

    @property
    def meta(self):
        return self._meta

    def dump(self):
        try:
            self._session_file.write(encode_json(dict(
                meta=self._dump_meta(),
                traces=self._dump_traces()
            )))
        except Exception:
            # traces could not be dumped for some reason,
            # just put exception into debug log
            logger.exception()

    def trace(self, trace):
        self._traces.append(trace)

    def trace_run_plesk_cli(self, exit_code, stdout, stderr, utility_name, args=None, stdin_content=None, env=None):
        """Trace execution of Plesk command line utility

        :type exit_code: int
        :type stdout: str | None
        :type stderr: str | None
        :type utility_name: str
        :type args: list[str]
        :type stdin_content: str | None
        :type env: dict[str, str] | None
        """
        self.trace(TraceRunPleskCli(exit_code, stdout, stderr, utility_name, args, stdin_content, env))

    def trace_copy_content(self, source_description, source_path, target_description, target_path):
        """Trace copy content operation

        :type source_description: str
        :type source_path: str
        :type target_description: str
        :type target_path: str
        """
        self.trace(TraceCopyContent(source_description, source_path, target_description, target_path))

    def _init_meta(self, data=None):
        raise NotImplementedError()

    def _dump_meta(self):
        return self.meta.get_data()

    def _dump_traces(self):
        data = []
        for trace in self._traces:
            data.append(dict(
                type=trace.type,
                data=trace.get_data()
            ))
        return data


class TracerMeta(object):
    def get_data(self):
        raise NotImplementedError


class Trace(object):
    FILTER_DATA_PLACEHOLDER_SENSITIVE = '<!-- sensitive data hidden -->'

    def __init__(self, *args, **kwargs):
        self._init_data(*args, **kwargs)
        self._filter_data()

    @property
    def type(self):
        raise NotImplementedError

    def get_data(self):
        raise NotImplementedError

    def _init_data(self, *args, **kwargs):
        raise NotImplementedError

    def _filter_data(self):
        pass


class TraceRunPleskCli(Trace):
    @property
    def type(self):
        return 'run_plesk_cli'

    def get_data(self):
        return dict(
            utility_name=self._utility_name,
            args=self._args,
            stdin_content=self._stdin_content,
            env=self._env,
            exit_code=self._exit_code,
            stdout=self._stdout,
            stderr=self._stderr
        )

    def _init_data(self, exit_code, stdout, stderr, utility_name, args, stdin_content, env):
        self._exit_code = exit_code
        self._stdout = stdout
        self._stderr = stderr
        self._utility_name = utility_name
        self._args = args
        self._stdin_content = stdin_content
        self._env = env

    def _filter_data(self):
        if Registry.get_instance().get_context().options.debug:
            return
        if self._stdout is not None and self._stdout != '':
            self._stdout = self.FILTER_DATA_PLACEHOLDER_SENSITIVE
        if self._stderr is not None and self._stderr != '':
            self._stderr = self.FILTER_DATA_PLACEHOLDER_SENSITIVE
        if self._stdin_content is not None and self._stdin_content != '':
            self._stdin_content = self.FILTER_DATA_PLACEHOLDER_SENSITIVE
        if self._env is not None and len(self._env) > 0:
            self._env = self.FILTER_DATA_PLACEHOLDER_SENSITIVE


class TraceCopyContent(Trace):
    @property
    def type(self):
        return 'copy_content'

    def get_data(self):
        return dict(
            source_description=self._source_description,
            source_path=self._source_path,
            target_description=self._target_description,
            target_path=self._target_path
        )

    def _init_data(self, source_description, source_path, target_description, target_path):
        self._source_description = source_description
        self._source_path = source_path
        self._target_description = target_description
        self._target_path = target_path
