# Copyright 2020 Canonical Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# For further info, check https://github.com/canonical/charmcraft

"""Set up logging."""

import logging
import os
import tempfile

from charmcraft import __version__

FORMATTER_SIMPLE = "%(message)s"
FORMATTER_DETAILED = "%(asctime)s  %(name)-30s %(levelname)-8s %(message)s"

_logger = logging.getLogger('charmcraft')
_logger.setLevel(logging.DEBUG)


class _MessageHandler:
    """Handle all the messages to the user.

    This class deals with several combination of the following dimensions:

    - the mode: quiet, normal or verbose
    - the output: sometimes, some messages, to the terminal; always to the file
    - the execution result: what happens if succeeded, raise a controlled error, or crashed
    """

    _modes = {
        'quiet': (logging.WARNING, FORMATTER_SIMPLE),
        'normal': (logging.INFO, FORMATTER_SIMPLE),
        'verbose': (logging.DEBUG, FORMATTER_DETAILED),
    }

    def __init__(self):
        self._stderr_handler = logging.StreamHandler()
        _logger.addHandler(self._stderr_handler)

        # autoset modes constants for simpler interface
        for k in self._modes:
            setattr(self, k.upper(), k)

    def init(self, initial_mode):
        """Initialize internal structures; this must be done before start logging."""
        self._set_filehandler()
        self.set_mode(initial_mode)

    def set_mode(self, mode):
        """Set logging in different modes."""
        self.mode = mode
        level, format_string = self._modes[mode]
        self._stderr_handler.setFormatter(logging.Formatter(format_string))
        self._stderr_handler.setLevel(level)
        if mode == self.VERBOSE:
            _logger.debug("Starting charmcraft version %s", __version__)

    def _set_filehandler(self):
        """Set the file handler to log everything to the temp file."""
        _, self._log_filepath = tempfile.mkstemp(prefix='charmcraft-log-')

        file_handler = logging.FileHandler(self._log_filepath)
        file_handler.setFormatter(logging.Formatter(FORMATTER_DETAILED))
        file_handler.setLevel(0)  # log eeeeeverything
        _logger.addHandler(file_handler)

        # a logger for only the file
        self._file_logger = logging.getLogger('charmcraft.guard')
        self._file_logger.propagate = False
        self._file_logger.addHandler(file_handler)
        self._file_logger.debug("Starting charmcraft version %s", __version__)

    def ended_ok(self):
        """Cleanup after successful execution."""
        os.unlink(self._log_filepath)

    def ended_interrupt(self):
        """Clean up on keyboard interrupt."""
        if self.mode == self.VERBOSE:
            _logger.exception("Interrupted.")
        else:
            _logger.error("Interrupted.")
        os.unlink(self._log_filepath)

    def ended_cmderror(self, err):
        """Report the (expected) problem and (maybe) logfile location."""
        if err.argsparsing:
            print(err)
        else:
            msg = "{} (full execution logs in {})".format(err, self._log_filepath)
            _logger.error(msg)

    def ended_crash(self, err):
        """Report the internal error and logfile location.

        Show just the error to the user, but send the whole traceback to the log file.
        """
        msg = "charmcraft internal error! {}: {} (full execution logs in {})".format(
            err.__class__.__name__, err, self._log_filepath)
        if self.mode == self.VERBOSE:
            # both to screen and file!
            _logger.exception(msg)
        else:
            # the error to screen and file, plus the traceback to the file
            _logger.error(msg)
            self._file_logger.exception('')


message_handler = _MessageHandler()
