# Python
import json
import logging
import os

# Django
from django.utils.translation import ugettext_lazy as _

# Django REST Framework
from rest_framework import serializers

# Tower
from awx.conf import fields, register, register_validate

logger = logging.getLogger('awx.main.conf')

register(
    'ACTIVITY_STREAM_ENABLED',
    field_class=fields.BooleanField,
    label=_('Enable Activity Stream'),
    help_text=_('Enable capturing activity for the activity stream.'),
    category=_('System'),
    category_slug='system',
    feature_required='activity_streams',
)

register(
    'ACTIVITY_STREAM_ENABLED_FOR_INVENTORY_SYNC',
    field_class=fields.BooleanField,
    label=_('Enable Activity Stream for Inventory Sync'),
    help_text=_('Enable capturing activity for the activity stream when running inventory sync.'),
    category=_('System'),
    category_slug='system',
    feature_required='activity_streams',
)

register(
    'ORG_ADMINS_CAN_SEE_ALL_USERS',
    field_class=fields.BooleanField,
    label=_('All Users Visible to Organization Admins'),
    help_text=_('Controls whether any Organization Admin can view all users and teams, '
                'even those not associated with their Organization.'),
    category=_('System'),
    category_slug='system',
)

register(
    'MANAGE_ORGANIZATION_AUTH',
    field_class=fields.BooleanField,
    label=_('Organization Admins Can Manage Users and Teams'),
    help_text=_('Controls whether any Organization Admin has the privileges to create and manage users and teams. '
                'You may want to disable this ability if you are using an LDAP or SAML integration.'),
    category=_('System'),
    category_slug='system',
)

register(
    'TOWER_ADMIN_ALERTS',
    field_class=fields.BooleanField,
    label=_('Enable Administrator Alerts'),
    help_text=_('Email Admin users for system events that may require attention.'),
    category=_('System'),
    category_slug='system',
)

register(
    'TOWER_URL_BASE',
    field_class=fields.URLField,
    schemes=('http', 'https'),
    allow_plain_hostname=True,  # Allow hostname only without TLD.
    label=_('Base URL of the Tower host'),
    help_text=_('This setting is used by services like notifications to render '
                'a valid url to the Tower host.'),
    category=_('System'),
    category_slug='system',
)

register(
    'REMOTE_HOST_HEADERS',
    field_class=fields.StringListField,
    label=_('Remote Host Headers'),
    help_text=_('HTTP headers and meta keys to search to determine remote host '
                'name or IP. Add additional items to this list, such as '
                '"HTTP_X_FORWARDED_FOR", if behind a reverse proxy. '
                'See the "Proxy Support" section of the Adminstrator guide for '
                'more details.'),
    category=_('System'),
    category_slug='system',
)

register(
    'PROXY_IP_WHITELIST',
    field_class=fields.StringListField,
    label=_('Proxy IP Whitelist'),
    help_text=_("If Tower is behind a reverse proxy/load balancer, use this setting "
                "to whitelist the proxy IP addresses from which Tower should trust "
                "custom REMOTE_HOST_HEADERS header values. "
                "If this setting is an empty list (the default), the headers specified by "
                "REMOTE_HOST_HEADERS will be trusted unconditionally')"),
    category=_('System'),
    category_slug='system',
)


def _load_default_license_from_file():
    try:
        license_file = os.environ.get('AWX_LICENSE_FILE', '/etc/tower/license')
        if os.path.exists(license_file):
            license_data = json.load(open(license_file))
            logger.debug('Read license data from "%s".', license_file)
            return license_data
    except Exception:
        logger.warning('Could not read license from "%s".', license_file, exc_info=True)
    return {}


register(
    'LICENSE',
    field_class=fields.DictField,
    default=_load_default_license_from_file,
    label=_('License'),
    help_text=_('The license controls which features and functionality are '
                'enabled. Use /api/v1/config/ to update or change '
                'the license.'),
    category=_('System'),
    category_slug='system',
)

register(
    'AD_HOC_COMMANDS',
    field_class=fields.StringListField,
    label=_('Ansible Modules Allowed for Ad Hoc Jobs'),
    help_text=_('List of modules allowed to be used by ad-hoc jobs.'),
    category=_('Jobs'),
    category_slug='jobs',
    required=False,
)

register(
    'ALLOW_JINJA_IN_EXTRA_VARS',
    field_class=fields.ChoiceField,
    choices=[
        ('always', _('Always')),
        ('never', _('Never')),
        ('template', _('Only On Job Template Definitions')),
    ],
    required=True,
    label=_('When can extra variables contain Jinja templates?'),
    help_text=_(
        'Ansible allows variable substitution via the Jinja2 templating '
        'language for --extra-vars. This poses a potential security '
        'risk where Tower users with the ability to specify extra vars at job '
        'launch time can use Jinja2 templates to run arbitrary Python.  It is '
        'recommended that this value be set to "template" or "never".'
    ),
    category=_('Jobs'),
    category_slug='jobs',
)

register(
    'AWX_PROOT_ENABLED',
    field_class=fields.BooleanField,
    label=_('Enable job isolation'),
    help_text=_('Isolates an Ansible job from protected parts of the system to prevent exposing sensitive information.'),
    category=_('Jobs'),
    category_slug='jobs',
)

register(
    'AWX_PROOT_BASE_PATH',
    field_class=fields.CharField,
    label=_('Job execution path'),
    help_text=_('The directory in which Tower will create new temporary '
                'directories for job execution and isolation '
                '(such as credential files and custom inventory scripts).'),
    category=_('Jobs'),
    category_slug='jobs',
)

register(
    'AWX_PROOT_HIDE_PATHS',
    field_class=fields.StringListField,
    required=False,
    label=_('Paths to hide from isolated jobs'),
    help_text=_('Additional paths to hide from isolated processes. Enter one path per line.'),
    category=_('Jobs'),
    category_slug='jobs',
)

register(
    'AWX_PROOT_SHOW_PATHS',
    field_class=fields.StringListField,
    required=False,
    label=_('Paths to expose to isolated jobs'),
    help_text=_('Whitelist of paths that would otherwise be hidden to expose to isolated jobs. Enter one path per line.'),
    category=_('Jobs'),
    category_slug='jobs',
)

register(
    'AWX_ISOLATED_CHECK_INTERVAL',
    field_class=fields.IntegerField,
    min_value=0,
    label=_('Isolated status check interval'),
    help_text=_('The number of seconds to sleep between status checks for jobs running on isolated instances.'),
    category=_('Jobs'),
    category_slug='jobs',
)

register(
    'AWX_ISOLATED_LAUNCH_TIMEOUT',
    field_class=fields.IntegerField,
    min_value=0,
    label=_('Isolated launch timeout'),
    help_text=_('The timeout (in seconds) for launching jobs on isolated instances.  '
                'This includes the time needed to copy source control files (playbooks) to the isolated instance.'),
    category=_('Jobs'),
    category_slug='jobs',
)

register(
    'AWX_ISOLATED_CONNECTION_TIMEOUT',
    field_class=fields.IntegerField,
    min_value=0,
    default=10,
    label=_('Isolated connection timeout'),
    help_text=_('Ansible SSH connection timeout (in seconds) to use when communicating with isolated instances. '
                'Value should be substantially greater than expected network latency.'),
    category=_('Jobs'),
    category_slug='jobs',
)

register(
    'AWX_ISOLATED_KEY_GENERATION',
    field_class=fields.BooleanField,
    default=True,
    label=_('Generate RSA keys for isolated instances'),
    help_text=_('If set, a random RSA key will be generated and distributed to '
                'isolated instances.  To disable this behavior and manage authentication '
                'for isolated instances outside of Tower, disable this setting.'),  # noqa
    category=_('Jobs'),
    category_slug='jobs',
)

register(
    'AWX_ISOLATED_PRIVATE_KEY',
    field_class=fields.CharField,
    default='',
    allow_blank=True,
    encrypted=True,
    read_only=True,
    label=_('The RSA private key for SSH traffic to isolated instances'),
    help_text=_('The RSA private key for SSH traffic to isolated instances'),  # noqa
    category=_('Jobs'),
    category_slug='jobs',
)

register(
    'AWX_ISOLATED_PUBLIC_KEY',
    field_class=fields.CharField,
    default='',
    allow_blank=True,
    read_only=True,
    label=_('The RSA public key for SSH traffic to isolated instances'),
    help_text=_('The RSA public key for SSH traffic to isolated instances'),  # noqa
    category=_('Jobs'),
    category_slug='jobs',
)

register(
    'AWX_TASK_ENV',
    field_class=fields.KeyValueField,
    default={},
    label=_('Extra Environment Variables'),
    help_text=_('Additional environment variables set for playbook runs, inventory updates, project updates, and notification sending.'),
    category=_('Jobs'),
    category_slug='jobs',
    placeholder={'HTTP_PROXY': 'myproxy.local:8080'},
)

register(
    'AWX_ROLES_ENABLED',
    field_class=fields.BooleanField,
    default=True,
    label=_('Enable Role Download'),
    help_text=_('Allows roles to be dynamically downlaoded from a requirements.yml file for SCM projects.'),
    category=_('Jobs'),
    category_slug='jobs',
)

register(
    'STDOUT_MAX_BYTES_DISPLAY',
    field_class=fields.IntegerField,
    min_value=0,
    label=_('Standard Output Maximum Display Size'),
    help_text=_('Maximum Size of Standard Output in bytes to display before requiring the output be downloaded.'),
    category=_('Jobs'),
    category_slug='jobs',
)

register(
    'EVENT_STDOUT_MAX_BYTES_DISPLAY',
    field_class=fields.IntegerField,
    min_value=0,
    label=_('Job Event Standard Output Maximum Display Size'),
    help_text=_(
        u'Maximum Size of Standard Output in bytes to display for a single job or ad hoc command event. `stdout` will end with `\u2026` when truncated.'),
    category=_('Jobs'),
    category_slug='jobs',
)

register(
    'SCHEDULE_MAX_JOBS',
    field_class=fields.IntegerField,
    min_value=1,
    label=_('Maximum Scheduled Jobs'),
    help_text=_('Maximum number of the same job template that can be waiting to run when launching from a schedule before no more are created.'),
    category=_('Jobs'),
    category_slug='jobs',
)

register(
    'AWX_ANSIBLE_CALLBACK_PLUGINS',
    field_class=fields.StringListField,
    required=False,
    label=_('Ansible Callback Plugins'),
    help_text=_('List of paths to search for extra callback plugins to be used when running jobs. Enter one path per line.'),
    category=_('Jobs'),
    category_slug='jobs',
)

register(
    'DEFAULT_JOB_TIMEOUT',
    field_class=fields.IntegerField,
    min_value=0,
    default=0,
    label=_('Default Job Timeout'),
    help_text=_('Maximum time in seconds to allow jobs to run. Use value of 0 to indicate that no '
                'timeout should be imposed. A timeout set on an individual job template will override this.'),
    category=_('Jobs'),
    category_slug='jobs',
)

register(
    'DEFAULT_INVENTORY_UPDATE_TIMEOUT',
    field_class=fields.IntegerField,
    min_value=0,
    default=0,
    label=_('Default Inventory Update Timeout'),
    help_text=_('Maximum time in seconds to allow inventory updates to run. Use value of 0 to indicate that no '
                'timeout should be imposed. A timeout set on an individual inventory source will override this.'),
    category=_('Jobs'),
    category_slug='jobs',
)

register(
    'DEFAULT_PROJECT_UPDATE_TIMEOUT',
    field_class=fields.IntegerField,
    min_value=0,
    default=0,
    label=_('Default Project Update Timeout'),
    help_text=_('Maximum time in seconds to allow project updates to run. Use value of 0 to indicate that no '
                'timeout should be imposed. A timeout set on an individual project will override this.'),
    category=_('Jobs'),
    category_slug='jobs',
)

register(
    'ANSIBLE_FACT_CACHE_TIMEOUT',
    field_class=fields.IntegerField,
    min_value=0,
    default=0,
    label=_('Per-Host Ansible Fact Cache Timeout'),
    help_text=_('Maximum time, in seconds, that stored Ansible facts are considered valid since '
                'the last time they were modified. Only valid, non-stale, facts will be accessible by '
                'a playbook. Note, this does not influence the deletion of ansible_facts from the database. '
                'Use a value of 0 to indicate that no timeout should be imposed.'),
    category=_('Jobs'),
    category_slug='jobs',
)

register(
    'LOG_AGGREGATOR_HOST',
    field_class=fields.CharField,
    allow_null=True,
    default=None,
    label=_('Logging Aggregator'),
    help_text=_('Hostname/IP where external logs will be sent to.'),
    category=_('Logging'),
    category_slug='logging',
)
register(
    'LOG_AGGREGATOR_PORT',
    field_class=fields.IntegerField,
    allow_null=True,
    default=None,
    label=_('Logging Aggregator Port'),
    help_text=_('Port on Logging Aggregator to send logs to (if required and not'
                ' provided in Logging Aggregator).'),
    category=_('Logging'),
    category_slug='logging',
    required=False
)
register(
    'LOG_AGGREGATOR_TYPE',
    field_class=fields.ChoiceField,
    choices=['logstash', 'splunk', 'loggly', 'sumologic', 'other'],
    allow_null=True,
    default=None,
    label=_('Logging Aggregator Type'),
    help_text=_('Format messages for the chosen log aggregator.'),
    category=_('Logging'),
    category_slug='logging',
)
register(
    'LOG_AGGREGATOR_USERNAME',
    field_class=fields.CharField,
    allow_blank=True,
    default='',
    label=_('Logging Aggregator Username'),
    help_text=_('Username for external log aggregator (if required).'),
    category=_('Logging'),
    category_slug='logging',
    required=False,
)
register(
    'LOG_AGGREGATOR_PASSWORD',
    field_class=fields.CharField,
    allow_blank=True,
    default='',
    encrypted=True,
    label=_('Logging Aggregator Password/Token'),
    help_text=_('Password or authentication token for external log aggregator (if required).'),
    category=_('Logging'),
    category_slug='logging',
    required=False,
)
register(
    'LOG_AGGREGATOR_LOGGERS',
    field_class=fields.StringListField,
    default=['awx', 'activity_stream', 'job_events', 'system_tracking'],
    label=_('Loggers Sending Data to Log Aggregator Form'),
    help_text=_('List of loggers that will send HTTP logs to the collector, these can '
                'include any or all of: \n'
                'awx - service logs\n'
                'activity_stream - activity stream records\n'
                'job_events - callback data from Ansible job events\n'
                'system_tracking - facts gathered from scan jobs.'),
    category=_('Logging'),
    category_slug='logging',
)
register(
    'LOG_AGGREGATOR_INDIVIDUAL_FACTS',
    field_class=fields.BooleanField,
    default=False,
    label=_('Log System Tracking Facts Individually'),
    help_text=_('If set, system tracking facts will be sent for each package, service, or '
                'other item found in a scan, allowing for greater search query granularity. '
                'If unset, facts will be sent as a single dictionary, allowing for greater '
                'efficiency in fact processing.'),
    category=_('Logging'),
    category_slug='logging',
)
register(
    'LOG_AGGREGATOR_ENABLED',
    field_class=fields.BooleanField,
    default=False,
    label=_('Enable External Logging'),
    help_text=_('Enable sending logs to external log aggregator.'),
    category=_('Logging'),
    category_slug='logging',
)
register(
    'LOG_AGGREGATOR_TOWER_UUID',
    field_class=fields.CharField,
    allow_blank=True,
    default='',
    label=_('Cluster-wide Tower unique identifier.'),
    help_text=_('Useful to uniquely identify Tower instances.'),
    category=_('Logging'),
    category_slug='logging',
)
register(
    'LOG_AGGREGATOR_PROTOCOL',
    field_class=fields.ChoiceField,
    choices=[('https', 'HTTPS/HTTP'), ('tcp', 'TCP'), ('udp', 'UDP')],
    default='https',
    label=_('Logging Aggregator Protocol'),
    help_text=_('Protocol used to communicate with log aggregator.  '
                'HTTPS/HTTP assumes HTTPS unless http:// is explicitly used in '
                'the Logging Aggregator hostname.'),
    category=_('Logging'),
    category_slug='logging',
)
register(
    'LOG_AGGREGATOR_TCP_TIMEOUT',
    field_class=fields.IntegerField,
    default=5,
    label=_('TCP Connection Timeout'),
    help_text=_('Number of seconds for a TCP connection to external log '
                'aggregator to timeout. Applies to HTTPS and TCP log '
                'aggregator protocols.'),
    category=_('Logging'),
    category_slug='logging',
)
register(
    'LOG_AGGREGATOR_VERIFY_CERT',
    field_class=fields.BooleanField,
    default=True,
    label=_('Enable/disable HTTPS certificate verification'),
    help_text=_('Flag to control enable/disable of certificate verification'
                ' when LOG_AGGREGATOR_PROTOCOL is "https". If enabled, Tower\'s'
                ' log handler will verify certificate sent by external log aggregator'
                ' before establishing connection.'),
    category=_('Logging'),
    category_slug='logging',
)
register(
    'LOG_AGGREGATOR_LEVEL',
    field_class=fields.ChoiceField,
    choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
    default='WARNING',
    label=_('Logging Aggregator Level Threshold'),
    help_text=_('Level threshold used by log handler. Severities from lowest to highest'
                ' are DEBUG, INFO, WARNING, ERROR, CRITICAL. Messages less severe '
                'than the threshold will be ignored by log handler. (messages under category '
                'awx.anlytics ignore this setting)'),
    category=_('Logging'),
    category_slug='logging',
)


def logging_validate(serializer, attrs):
    if not serializer.instance or \
            not hasattr(serializer.instance, 'LOG_AGGREGATOR_HOST') or \
            not hasattr(serializer.instance, 'LOG_AGGREGATOR_TYPE'):
        return attrs
    errors = []
    if attrs.get('LOG_AGGREGATOR_ENABLED', False):
        if not serializer.instance.LOG_AGGREGATOR_HOST and not attrs.get('LOG_AGGREGATOR_HOST', None) or\
                serializer.instance.LOG_AGGREGATOR_HOST and not attrs.get('LOG_AGGREGATOR_HOST', True):
            errors.append('Cannot enable log aggregator without providing host.')
        if not serializer.instance.LOG_AGGREGATOR_TYPE and not attrs.get('LOG_AGGREGATOR_TYPE', None) or\
                serializer.instance.LOG_AGGREGATOR_TYPE and not attrs.get('LOG_AGGREGATOR_TYPE', True):
            errors.append('Cannot enable log aggregator without providing type.')
    if errors:
        raise serializers.ValidationError(_('\n'.join(errors)))
    return attrs


register_validate('logging', logging_validate)
