#!/usr/bin/env python
# -*- coding: utf-8 -*-
import json
import sys
from configparser import ConfigParser
from configparser import ExtendedInterpolation
from contextlib import contextmanager
from datetime import date
from os import environ
from os import path
from typing import Dict


def default_config(allow_no_value: bool = True):
    """Generates a default configuration

    Parameters
    ==========
    allow_no_value : bool
      Defaults to True. If set to False, keys without values will
      throw a configparser.ParsingError.

    Returns
    =======
    config : configparser.ConfigParser
      The default configuration

    Testing
    -------

    Generate a default configuration
    >>> config = default_config()

    >>> config._sections['Logging'].get('level')
    'debug'
    """
    config = ConfigParser(interpolation=ExtendedInterpolation(),
                          allow_no_value=allow_no_value)
    # this forces our keys to be read case-sensitive
    config.optionxform = str
    config.read_string("""
  [Cloudability]
  token_arn = "" # populated during init
  base_url = https://app.cloudability.com/api

  [FivePillars]
  token_arn = "" # populated during init
  base_url = ""

  [HelpImages]
  base_url = https://downloads.pearsoncloudmanagement.com/slater/prod/static/images

  [Logging]
  level = debug

  [PagerDuty]
  token_arn = "" # populated during init
  base_url = https://api.pagerduty.com

  [Qualys]
  output_format = csv
  athena_bucket = prsn-vm-data-us-east-1-747207162522

  [Routing]
  destination_queue_url = "" # populated during init

  [Slack]
  app_id = "A12345678"
  archiver_authorized_usergroup_handles = odc-team,chatops-engineers,admins
  signing_secret_arn = "" # populated during init
  verification_token_arn = "" # populated during init
  bearer_token_arn = "" # populated during init
  user_token_arn = "" # populated during init
  auth_arn = "" # populated during init

  [SLater]
  auth_url = https://slater-auth.auth.us-east-1.amazoncognito.com
  base_url = https://api.pearsoncloudmanagement.com
  db_version =
  accounts_api = ${base_url}/slater/accounts
  accounttypes_api = ${base_url}/slater/accounttypes
  accountapplications_api = ${base_url}/slater/accountapplications
  accountcontacts_api = ${base_url}/slater/accountcontacts
  applications_api = ${base_url}/slater/applications
  applicationcontacts_api = ${base_url}/slater/applicationcontacts
  batch_api = ${base_url}/slater/batchgroup
  batcherrors_api = ${base_url}/slater/batcherrors
  businessunits_api = ${base_url}/slater/businessunits
  contacts_api = ${base_url}/slater/contacts
  contact_types_api = ${base_url}/slater/contacttypes
  customfields_api = ${base_url}/slater/customfields
  accountcustomfields_api = ${base_url}/slater/accountcustomfields
  applicationcustomfields_api = ${base_url}/slater/applicationcustomfields
  exports_api = ${base_url}/slater/export
  lobs_api = ${base_url}/slater/lobs
  login_api = ${auth_url}/oauth2/token
  masterpayers_api = ${base_url}/slater/masterpayers
  towers_api = ${base_url}/slater/towers
  slater_authorized_usergroup_handles = slater-writers,slater-admins,slater-finops,slater-cds,slater-editors,slater-read-only

  [MasterPayerLookup]
  ^150047381276$ = CIO
  ^958342668309$ = Assessments
  ^Assessments$ = Assessments
  ^CIO$ = CIO
  ^(?!150047381276|958342668309|Assessments|CIO).*$ =

  [ServiceNow]
  url = https://pearsonstsprod.service-now.com

  [Sendgrid]
  token_arn = "" # populated during init
  base_url = https://api.sendgrid.com
  token = ""

  [Tableau]
  token_arn = "" # populated during init
    """)

    return config

def init_config(config_file: str = 'iobot.ini', env: Dict = None,
                allow_no_value: bool = True):
    """Create a config object and return it

    Parameters
    ==========
    config_file : str
      Defaults to iobot.ini.

    Returns
    =======
    config : configparser.ConfigParser
      The initialized configuration

    Testing
    -------

    >>> os.environ['STACK_NAME'] = 'iobots-stack'

    >>> testing.export_secret_arns(os.environ['STACK_NAME'])

    >>> env = dict(os.environ)

    >>> config = init_config(config_file='environments/unit.ini', env=env)

    >>> config['Slack']['app_id']
    'A015B50KKAA'

    >>> assert(config['Slack']['signing_secret_arn'] is not None), f"expected 'signing_secret_arn' to not be None"
    """
    config = default_config()
    if not path.exists(config_file):
        with open(config_file, 'w') as configfile:
            config.write(configfile)
    else:
        config.read(config_file)

    config['Slack']['signing_secret_arn'] = get_envvar(
        "SLACK_SIGNING_SECRET_ARN", env=env)
    config['Slack']['verification_token_arn'] = get_envvar(
        "VERIFICATION_TOKEN_ARN", env=env)
    config['Slack']['bearer_token_arn'] = get_envvar(
        "SLACK_BEARER_TOKEN_ARN", env=env)
    config['Slack']['user_token_arn'] = get_envvar(
        "SLACK_USER_TOKEN_ARN", env=env)
    config['Slack']['auth_arn'] = get_envvar(
        "SLACK_AUTH_ARN", env=env)
    config['PagerDuty']['token_arn'] = get_envvar(
        "PAGERDUTY_TOKEN_ARN", env=env)
    config['Routing']['destination_queue_url'] = get_envvar(
        "DESTINATION_QUEUE_URL", env=env)
    config['Tableau']['token_arn'] = get_envvar(
        "TABLEAU_TOKEN_ARN", env=env)
    config['Cloudability']['token_arn'] = get_envvar(
        "CLOUDABILITY_TOKEN_ARN", env=env)
    config['Sendgrid']['token_arn'] = get_envvar(
        "SENDGRID_TOKEN_ARN", env=env)

    return config


def get_envvar(environment_variable: str, env: Dict = None) -> str:
    """ Fetch a variable or raise an exception

    Parameters
    ==========
    environment_variable : str
      The environment variable you want to retrieve.

    env: Dict
      You can use dict(os.environ) to pass a copy of the current environment,
      or a dictionary generated by the method of your choosing.

    Returns
    =======
    var : str
      The environment variable specified by environment_variable
      or raises an exception.

    Raises
    ======
    MissingVariableException : Exception

    Testing
    -------

    >>> os.environ['STACK_NAME'] = 'iobots-stack'

    >>> testing.export_secret_arns(os.environ['STACK_NAME'])

    >>> get_envvar('STACK_NAME')
    'iobots-stack'

    >>> get_envvar('STACK_NAME', env=dict(os.environ))
    'iobots-stack'

    >>> del os.environ['STACK_NAME']
    """
    if env:
        envvar: str = f'{env.get(environment_variable)}'
        if envvar:
            return envvar
        else:
            raise Exception(f'missing variable {environment_variable}')
    else:
        if environment_variable in environ:
            return f'{environ.get(environment_variable)}'
        else:
            raise Exception(
                f'missing environment variable {environment_variable}')


def pad_if_blank_or_none(input: str) -> str:
    """ Test blank and none values

    Parameters
    ==========
    input : str
      The value you want to pad if None

    Returns
    =======
    input : str
      A Padded version of input if None

    Testing
    -------

    >>> none = pad_if_blank_or_none(None)

    >>> assert (len(list(none)) == 1), f"expected one, got {len(list(none))}"

    >>> blank = pad_if_blank_or_none('')

    >>> assert (len(list(blank)) == 1), f"expected one, got {len(list(blank))}"
    """
    if input == 0:
        return str(input)
    if not input:
        return str(' ')
    elif input == '':
        return str(' ')
    else:
        return str(input)


def today_if_blank_or_none(input) -> str:
    """ Validate we return today's date if input is null, otherwise return input

    Parameters
    ==========
    input : float|str|DateTime ???
      Value to check if None

    Returns
    =======
    input : str
      Today's date if the input value was None, otherwise the original Value.
      Both values will have their '.strip()' method called.

    Testing
    -------

    >>> from datetime import timedelta
    >>> today = date.today()
    >>> couple_days_ago = today - timedelta(days=2)
    >>> zero = 0
    >>> assert (str(today) == today_if_blank_or_none(zero) ), f'expected "{today}", got "{today_if_blank_or_none(zero)}"'
    >>> assert (str(today) == today_if_blank_or_none(None) ), f'expected "{today}", got "{today_if_blank_or_none(None)}"'
    >>> assert (str(today) == today_if_blank_or_none(today) ), f'expected "{today}", got "{today_if_blank_or_none(today)}"'
    >>> assert (str(couple_days_ago) == today_if_blank_or_none(couple_days_ago) ), f'expected "{today}", got "{today_if_blank_or_none(couple_days_ago)}"'
    """
    if not input:
        return str(date.today()).strip()
    return str(input).strip()


def format_cloudformation_overrides_for_sam(parameters_files: list) -> str:
    """ Converts a cloudformation parameter template to sam parameters

    Parameters
    ==========
    parameters_files : str
      List of string file names (including their path) to load secrets into
      key=value parameter sets.

    Returns
    =======
    overrides : str
      A space delimited string of key=value pairs

    Testing
    -------

    >>> format_cloudformation_overrides_for_sam(['secrets/null.json'])  # noqa E501
    'ParameterKey=ServiceNowUsername,ParameterValue= ParameterKey=ServiceNowPassword,ParameterValue= ParameterKey=SlackBearerToken,ParameterValue= ParameterKey=SlackVerificationToken,ParameterValue= ParameterKey=PagerDutyToken,ParameterValue= ParameterKey=TableauToken,ParameterValue= ParameterKey=CloudabilityToken,ParameterValue= ParameterKey=GrafanaCloudToken,ParameterValue= ParameterKey=SendgridToken,ParameterValue='
    """
    overrides: list = []
    for parameter_file in parameters_files:
        with open(parameter_file, 'r') as input:
            for parameter in json.loads(input.read()):
                overrides.append(
                    f'ParameterKey={parameter["ParameterKey"]},ParameterValue={parameter["ParameterValue"]}')

        return " ".join(overrides)


def format_object_size(actual: float = 0.0, suffix: str = 'B', include_prefix: bool = False):
    """

    Parameters
    ==========
    actual : float
      Input value to format

    suffix : str
      The size of the parameter 'actual', defaults to (B)ytes

    include_prefix : bool
      Should the returned value be returned as a formatted string of float?

    Returns
    =======
    actual : float
      Formatted version of parameter 'actual'

    Testing
    -------

    """
    for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']:
        if abs(actual) < 1024.0:
            if include_prefix:
                return "%3.1f%s%s" % (actual, unit, suffix)
            else:
                return actual
        actual /= 1024.0

    if include_prefix:
        return "%.1f%s%s" % (actual, 'Y', suffix)
    else:
        return actual


def exceeds_max_filesize(actual_size: float = 0.0, max_size: float = 5000 * 1024, include_prefix: bool = True) -> (bool, str, str):
    """
      Parameters
      ==========
      actual_size : float
        Size you want to compare against 'max_size' parameter

      max_size : float
        Size you want to compare against 'actual_size' parameter. Default is 5MB

      include_prefix : bool
        Return values with human readable formatting?

      Returns
      =======
      exceeds : bool
        Formatted version of max_size

      actual_size : str
        Formatted version of actual_size

      max_size : str
        Formatted version of max_size

      Testing
      -------

      >>> exceeds_max_filesize(actual_size=2000000, max_size=1000000)
      (True, '1.9MB', '976.6KB')
      >>> exceeds_max_filesize(actual_size=1000000, max_size=2000000)
      (False, '976.6KB', '1.9MB')
      >>> exceeds_max_filesize(actual_size=2000000, max_size=1000000,include_prefix=False)
      (True, 1.9073486328125, 976.5625)

    """
    return abs(actual_size) >= abs(max_size), format_object_size(actual_size, include_prefix=include_prefix), format_object_size(max_size, include_prefix=include_prefix)


@contextmanager
def redirect_stderr_to_stdout():
    old_stderr = sys.stderr
    sys.stderr = sys.stdout
    try:
        yield
    finally:
        sys.stderr = old_stderr
