import logging
import sys
import traceback

from contextlib import contextmanager


logger = logging.getLogger(__name__)


class AbstractBaseReporter:
  '''
  An abstract base class that defines the interface for reporting issues and provides a 'factory' for instantiating
  implementations from the 'impl' sub-diretory.

  Example usage:
  >>> reporter = ... # Instance of AbstractBaseReporter
  >>> reporter.report_issue('Something happened')
  '''


  def report_issue(self, msg, **kwargs):
    '''
    Note: sub-classes should not override this method, but rather the '_report_issue_unsafe' method

    Parameters:
      msg: the main mesage describing the issue. The message is typically used as a de-dupe key, so avoid including too
        many specific details.
      [domain]: the domain on which the service is running
      [exception]: contains information about the exception that occurred (if there was one)
        {
          'msg': <str>, # Typically derived by using 'repr' on the exception
          'traceback': [ <str> ] # A list containing lines of the traceback
        }
      [request]: contains information about the reuest (if this issue was reported in that context)
        {
          'route': <str>, # Name of the route
          'profile': <str>, # The JSON-serialized JWT profile associalted with the request (potentially None)
          'input_data': <str>, # The JSON-serialized request input data (potentially None)
          'response_code': <int>, # The HTTP status code of the response
          'response_data': <str> # The JSON-serialized response data (potentially None)
        }
      [task]: contains information about the task (if this issue was reported in that context)
        {
          'task_run_id': <uuid>,
          'source': { 'pkg_id': <uuid>, 'commit_id': <str>, 'config_id': <uuid> },
          'task_type_id': <str>,
          'start': <datetime>,
          'n_beats': <int>,
          'deadline': <datetime>,
          'input': {
            'args': [ <str> ],
            'kwargs': { <str>: <str> }
          }
        }
      [metadata]: the OPService metadata
        {
          'pkg_id': <uuid_str>,
          'commit_id': <str>,
          'config_id': <uuid_str>
        }
      [count]: the count over time range that this issue has been occurring
        {
          'n': <int>,
          't_start': <datetime>,
          't_end': <datetime>
        }
    '''

    try:
      self._report_issue_unsafe(msg, **kwargs)
    except:
      logger.exception('Failed to report issue')


  def _report_issue_unsafe(self, msg, domain=None, exception=None, request=None, task=None, metadata=None, count=None):
    raise NotImplementedError()


  @contextmanager
  def report_exceptions(self, msg, log=None, **kwargs):
    try:
      yield
    except Exception:
      if log:
        log.exception(msg)
      try:
        _, exc_value, exc_traceback = sys.exc_info()
        stack_summary = traceback.extract_tb(exc_traceback)
        kwargs['exception'] = { 'msg': repr(exc_value), 'traceback': stack_summary.format() }
        self.report_issue(msg, **kwargs)
      except:
        logger.exception('Failed to report exception')
