import argparse
import contextlib
import io
import time
import logging
import random
import re
import sys

from . import constants


def validate_fqdn(fqdn):
  if not re.match(r"^[-.a-zA-Z0-9]+\.[-a-zA-Z0-9]{2,}$", fqdn):
    raise Exception("Unacceptable FQDN \"{}\"".format(fqdn))


class Timeout:
  def __init__(self, duration):
    self._duration = duration
    self.reset()

  def reset(self):
    self._start = time.monotonic()

  @property
  def duration(self):
    return self._duration

  @property
  def elapsed(self):
    """Get number of seconds until now.

    """
    return time.monotonic() - self._start

  @property
  def remaining(self):
    """Get number of remaining seconds.

    """
    return self._duration - self.elapsed

  @property
  def expired(self):
    """Determine whether timeout has expired.

    """
    return self.remaining < 0


class Delayer:
  def __init__(self, initial, max_):
    self._delay = initial
    self._max = max_

  def sleep(self, remaining):
    delay = max(1, min(self._delay * 1.25, remaining, self._max))

    # Add a small amount of jitter
    delay *= 0.9 + (0.2 * random.random())

    self._delay = delay

    logging.debug("Sleeping %0.1f seconds (%0.1f seconds remaining)", delay, remaining)

    time.sleep(delay)


class _Reporter:
  def __init__(self):
    self._exit_code = constants.STATE_UNKNOWN
    self._output = None
    self._metrics = None

  @property
  def exit_code(self):
    return self._exit_code

  @property
  def exit_code_text(self):
    fallback = constants.STATE_TEXT[constants.STATE_UNKNOWN]

    return constants.STATE_TEXT.get(self._exit_code, fallback)

  @property
  def output(self):
    return self._output

  @property
  def metrics(self):
    return self._metrics

  def exit(self, code, output, metrics=None):
    self._exit_code = code
    self._output = output
    self._metrics = metrics

    sys.exit(self._exit_code)


def _iter_any(value):
  if isinstance(value, (list, tuple)):
    return list(value)
  else:
    return [value]


@contextlib.contextmanager
def NagiosOutputFile(nagios_output):
  logging.basicConfig(level=logging.NOTSET,
                      format="%(asctime)s %(message)s")

  if nagios_output is None:
    fh = sys.stdout
  else:
    fh = open(nagios_output, "w")

  ctx = _Reporter()
  logbuf = io.StringIO()
  rootlogger = logging.getLogger(name=None)

  try:
    handler = logging.StreamHandler(stream=logbuf)
    handler.setLevel(logging.INFO)
    handler.setFormatter(logging.Formatter(fmt="%(asctime)s %(message)s"))

    with contextlib.ExitStack() as exstack:
      # Add custom handler while check runs
      rootlogger.addHandler(handler)
      exstack.callback(rootlogger.removeHandler, handler)
      try:
        yield ctx.exit
      except Exception as err:
        logging.exception("Exception caught")
        ctx.exit(constants.STATE_CRITICAL, str(err))
  finally:
    fh.write(ctx.exit_code_text)
    fh.write(" ")
    fh.write(", ".join(str(i) for i in _iter_any(ctx.output)))
    if ctx.metrics:
      fh.write(" | ")
      fh.write(" ".join(str(i) for i in _iter_any(ctx.metrics)))
    fh.write("\n")

    handler.flush()

    fh.write("\nLog messages:\n")
    fh.write(logbuf.getvalue())
    fh.flush()


def add_verbose_argument(parser):
  parser.add_argument("-v", "--verbose", action="count", default=0,
                      help="Increase output verbosity")


def setup_basic_logging(verbose):
  if verbose > 1:
    level = logging.NOTSET
  elif verbose:
    level = logging.INFO
  else:
    level = logging.CRITICAL

  logging.basicConfig(level=level)


def add_token_arguments(parser):
  tokgroup = parser.add_mutually_exclusive_group(required=False)
  tokgroup.add_argument("--token", help="Bearer token for authentication")
  tokgroup.add_argument("--token-from", metavar="FILE",
                        type=argparse.FileType(mode="r"),
                        help=("Path to file containing bearer token for"
                              " authentication"))


def extract_token_argument(args):
  """Return token specified on command line.

  """
  if args.token_from is None:
    return args.token

  return args.token_from.read().rstrip()


def raise_for_elasticsearch_response(resp):
  resp.raise_for_status()

  if resp.is_redirect:
    raise Exception("Unexpected redirect with status code {}".
                    format(resp.status_code))


# vim: set sw=2 sts=2 et :