import contextlib
import json

from op_service_client import OPServiceClient

from app.config import get_config


config = get_config()
url = config.get('op_dev_api_url', 'https://op-dev-manager-service.op-dev.io')
auth = { 'route': 'get-jwt-from-secret', 'params': config }
op_dev_service = OPServiceClient(url, auth)


# TODO: some of this logic is still obsolete with the switch from nginx / flask to gunicorn / starlette...

# Command-line color codes:
# https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html


DEFAULT_LOG_LEVEL_STYLES = {
  'TRACE': ("\033[90m", "\033[0m"),
  'DEBUG': ("\033[32m", "\033[0m"),
  'WARNING': ("\033[33m", "\033[0m"),
  'ERROR': ("\033[31m", "\033[0m"),
  'CRITICAL': ("\033[91m", "\033[0m")
}


DEFAULT_LOG_NAME_STYLES = {
  'OP-DEV': ("\033[90m", "\033[0m"),
  'OP-TEST': ("\033[1m", "\033[0m"),
  'op-request': ("\033[36m", "\033[0m")
}


def parse_log_msg(msg):
  '''
  Log messaegs produced using the op-dev pattern should be a string prefixed with '[OP]' and followed by a JSON object
  that should contain the following keys:
    l: the level with which the message was logged - DEBUG, INFO, WARNING, ERROR, CRITICAL, or None
    n: the name of the logger, as in logging.getLogger(<name>) - will be 'root' instead of None
    i: the instance ID of the service from which this message was logged
    s: the python source file path (relative to the module root)
    #: the line number from the above source file
    m: the message string (potentially contains newlines)

  Valid OP log messages will return the dictionary containing those keys. Other messages will only have the 'm'
  property set to the 'msg' as passed in.

  Note: CloudWatch may incorrectly conflate log messages that involve characters like '\r' (or possibly other
  characters with special meaning) that could muck with our log lines. The impact is that our formatted log messages
  may not get parsed correctly if our tag follows one of these characters. Thus, if the '\r' character precedes a known
  prefix, we ignore the content up to and including that character. This makes sense, as that output would have been
  overwritten in the terminal anyway, even though it contains some information. OP log chunks always end with a
  newline, so there is no need to check for '\r' after a prefix token is found.
  '''
  for i, char in enumerate(msg):
    if i == 0 or char == '\r':
      start_at_i = 0 if i == 0 else i + 1
      if msg[start_at_i:(start_at_i + 4)] == '[OP]':
        with contextlib.suppress(Exception):
          return json.loads(msg[start_at_i + 4:])
  return { 'm': msg }


class OPLogFormatter(object):

  def __init__(self, lvl_styles=DEFAULT_LOG_LEVEL_STYLES, name_styles=DEFAULT_LOG_NAME_STYLES, tz=None, time_format=None):
    self.lvl_styles = lvl_styles
    self.name_styles = name_styles
    self.tz = tz
    self.time_format = time_format

  def _is_op_msg(self, msg_dict):
    return 'i' in msg_dict

  def format_time(self, t):
    local_t = t.astimezone(self.tz)
    if self.time_format:
      return local_t.strftime(self.time_format)
    return local_t.isoformat()

  def format(self, t, parsed_msg):
    # Styles for log names override those for the log levels
    # TODO: consider making this additive rather than replacing other styles
    style = ('', '')
    style = self.lvl_styles.get(parsed_msg.get('l'), style)
    style = self.name_styles.get(parsed_msg.get('n'), style)
    style_prefix, style_suffix = style
    t_str = self.format_time(t)

    if self._is_op_msg(parsed_msg):
      # OP messages get a prefixed header row containing the metadata
      prefix = '[' + t_str + '] ' + ('[%(l)s] [%(n)s] [%(i)s] [%(s)s#%(#)s]\n' % parsed_msg)
    else:
      # Non-OP messages get the timestamp printed on every line (since we don't know the boundaries of the message)
      prefix = '[' + t_str + '] '

    if parsed_msg.get('n') == 'op-request':
      # Apply special formatting to op-request log messages
      try:
        request_data = json.loads(parsed_msg['m'])
        if request_data['_'] == 0:
          msg = 'Started request to route: %(r)s' % request_data
        else:
          msg = 'Finished request to route: %(r)s [%(c)s]' % request_data
          if 'pv' in request_data:
            msg += '\nInput profile (validated):\n%s' % json.dumps(request_data['pv'], indent=2)
          elif 'p' in request_data:
            msg += '\nInput profile:\n%s' % json.dumps(request_data['p'], indent=2)
          if 'iv' in request_data:
            msg += '\nInput data (validated):\n%s' % json.dumps(request_data['iv'], indent=2)
          elif 'i' in request_data:
            msg += '\nInput data:\n%s' % json.dumps(request_data['i'], indent=2)
          if 'o' in request_data:
            msg += '\nResponse data:\n%s' % json.dumps(request_data['o'], indent=2)
      except:
        # Only for legacy services
        msg = parsed_msg['m']
    else:
      msg = parsed_msg['m']

    return style_prefix + prefix + msg + style_suffix
