from contextlib import contextmanager

from parallels.core import MigratorInterruptException, get_logger, MigrationNoContextError
from parallels.core.reports.model.issue import Issue
from parallels.core.utils.common import is_empty, safe_string_repr
from parallels.core import messages

logger = get_logger(__name__)


class SafeObject(object):
    ACTION_STATUS_ERROR = 'error'
    ACTION_STATUS_WARNING = 'warning'
    ACTION_STATUS_SUCCESS = 'success'

    def __init__(self):
        self._has_errors = False
        self._has_warnings = False
        # list of actions with their status for the current object
        # key is action class, value is constant from ACTION_STATUS_*
        self._actions = {}

    def add_issue(self, issue, storage_issue_type=None, action=None):
        """Add issue to the list of issues for the current object.

        Optionally pass action's class object (ActionClass.__class__) in action parameter.

        :type issue: parallels.core.reports.model.issue.Issue
        :type storage_issue_type: str | unicode | None
        :type action: object | None
        :rtype: None
        """
        self._set_common_severity(issue, action)
        self._add_issue_to_storage(issue, issue_type=storage_issue_type)

    def add_issues(self, issues, storage_issues_type=None, action=None):
        """Add issues to the list of issues for the current object.

        Optionally pass action's class object (ActionClass.__class__) in action parameter.

        :type issues: list[parallels.core.reports.model.issue.Issue]
        :type storage_issues_type: str | unicode | None
        :type action: object | None
        :rtype: None
        """
        for issue in issues:
            self._set_common_severity(issue, action)
        self._add_issues_to_storage(issues, issues_type=storage_issues_type)

    @contextmanager
    def try_safe(self, failure_message, issue_severity=Issue.SEVERITY_ERROR, action=None):
        """Context manager to execute operation safely in the code block.

        On any exception it is added as an issue to the current object's issue list, and migrator continues execution.

        Optionally pass action's class object (ActionClass.__class__) in action parameter.

        :type failure_message: str | unicode
        :type issue_severity: str | unicode
        :type action: object | None
        """
        log_function = logger.fwarn if issue_severity == Issue.SEVERITY_WARNING else logger.ferror

        try:
            yield
        except MigratorInterruptException:
            raise
        except SafeObjectError as e:
            logger.exception()
            if not is_empty(e.details):
                log_function(messages.FAILURE_MESSAGE, message=e.message, details=e.details)
            else:
                log_function(e.message)
            self.add_issue(
                Issue(
                    'object_issue', issue_severity,
                    problem_text=e.message,
                    details_text=e.details
                ),
                action=action
            )
        except Exception as e:
            logger.exception()
            details = safe_string_repr(e)
            if not is_empty(details):
                log_function(messages.FAILURE_MESSAGE, message=failure_message, details=details)
            else:
                log_function(e.message)

            self.add_issue(
                Issue(
                    'object_issue', issue_severity,
                    problem_text=failure_message,
                    details_text=details
                ),
                action=action
            )

    def set_executed(self, action):
        """Mark action as executed (if no failure - it is marked as success, on any failure status is not touched)

        Pass action's class object (ActionClass.__class__) in action parameter.

        :type action: object
        :rtype: None
        """
        if action not in self._actions:
            self._actions[action] = self.ACTION_STATUS_SUCCESS

    def is_action_successful(self, action):
        """Whether specified action was executed successfully

        Pass action's class object (ActionClass.__class__) in action parameter.

        :type action: object
        :rtype: bool
        """
        return self._actions.get(action) == self.ACTION_STATUS_SUCCESS

    @property
    def has_errors(self):
        """Whether the object had errors during current migrator execution

        :rtype: bool
        """
        return self._has_errors

    @property
    def has_warnings(self):
        """Whether the object had warnings during current migrator execution

        :rtype: bool
        """
        return self._has_warnings

    def _add_issue_to_storage(self, issue, issue_type=None):
        """Add issue to persistent issue storage, which is used by GUI to display object issues

        :type issue: parallels.core.reports.model.issue.Issue
        :type issue_type: str | unicode | None
        :rtype: None
        """
        raise NotImplementedError()

    def _add_issues_to_storage(self, issues, issues_type=None):
        """Add issues to persistent issue storage, which is used by GUI to display object issues

        :type issues: list[parallels.core.reports.model.issue.Issue]
        :type issues_type: str | unicode | None
        :rtype: None
        """
        raise NotImplementedError()

    def _set_common_severity(self, issue, action):
        """Analyze the issue and adjust the highest detected severity

        :type issue: parallels.core.reports.model.issue.Issue
        :type action: object | None
        :rtype: None
        """
        if issue.severity == Issue.SEVERITY_ERROR:
            self._has_errors = True
            self._actions[action] = self.ACTION_STATUS_ERROR
        elif issue.severity == Issue.SEVERITY_WARNING:
            self._has_warnings = True
            if self._actions.get(action) != self.ACTION_STATUS_ERROR:
                self._actions[action] = self.ACTION_STATUS_WARNING


class SafeObjectError(MigrationNoContextError):
    pass
