"""
Ansible action plugin to ensure inventory variables are set
appropriately and no conflicting options have been provided.
"""
import collections
import six

from ansible.plugins.action import ActionBase
from ansible import errors


FAIL_MSG = """A string value that appears to be a file path located outside of
{} has been found in /etc/origin/master/master-config.yaml.
In 3.10 and newer, all files needed by the master must reside inside of
those directories or a subdirectory or it will not be readable by the
master process. Please migrate all files needed by the master into
one of {} or a subdirectory and update your master configs before
proceeding. The string found was: {}
***********************
NOTE: the following items do not need to be migrated, they will be migrated
for you: {}"""


ITEMS_TO_POP = (
    ('auditConfig', 'policyConfiguration'),
    ('oauthConfig', 'identityProviders'),
    ('kubernetesMasterConfig', 'apiServerArguments', 'etcd-servers-overrides')
)
# Create csv string of dot-separated dictionary keys:
# eg: 'oathConfig.identityProviders, something.else.here'
MIGRATED_ITEMS = ", ".join([".".join(x) for x in ITEMS_TO_POP])

ALLOWED_DIRS = (
    '/dev/null',
    '/etc/origin/master/',
    '/var/lib/origin',
    '/etc/origin/cloudprovider',
    '/etc/origin/kubelet-plugins',
    '/usr/libexec/kubernetes/kubelet-plugins',
    '/var/log/origin',
)

ALLOWED_DIRS_STRING = ', '.join(ALLOWED_DIRS)


def pop_migrated_fields(mastercfg):
    """Some fields do not need to be searched because they will be migrated
    for users automatically"""
    # Walk down the tree and pop the specific item we migrate / don't care about
    for field_path in ITEMS_TO_POP:
        pop_migrated_field(mastercfg, field_path)


def pop_migrated_field(mastercfg, field_path):
    """Remove field at given path from config"""
    field = mastercfg
    for sub_field in field_path:
        parent_field = field
        if sub_field not in field:
            return
        field = field[sub_field]
    parent_field.pop(field_path[-1])


def do_item_check(val, strings_to_check):
    """Check type of val, append to strings_to_check if string, otherwise if
    it's a dictionary-like object call walk_mapping, if it's a list-like
    object call walk_sequence, else ignore."""
    if isinstance(val, six.string_types):
        strings_to_check.append(val)
    elif isinstance(val, collections.Sequence):
        # A list-like object
        walk_sequence(val, strings_to_check)
    elif isinstance(val, collections.Mapping):
        # A dictionary-like object
        walk_mapping(val, strings_to_check)
    # If it's not a string, list, or dictionary, we're not interested.


def walk_sequence(items, strings_to_check):
    """Walk recursively through a list, items"""
    for item in items:
        do_item_check(item, strings_to_check)


def walk_mapping(map_to_walk, strings_to_check):
    """Walk recursively through map_to_walk dictionary and add strings to
    strings_to_check"""
    for _, val in map_to_walk.items():
        do_item_check(val, strings_to_check)


def check_strings(strings_to_check):
    """Check the strings we found to see if they look like file paths and if
    they are, fail if not start with /etc/origin/master"""
    for item in strings_to_check:
        if item.startswith('/') or item.startswith('../'):
            matches = 0
            for allowed in ALLOWED_DIRS:
                if item.startswith(allowed):
                    matches += 1
            if matches == 0:
                raise errors.AnsibleModuleError(
                    FAIL_MSG.format(ALLOWED_DIRS_STRING,
                                    ALLOWED_DIRS_STRING,
                                    item, MIGRATED_ITEMS))


# pylint: disable=R0903
class ActionModule(ActionBase):
    """Action plugin to validate no files are needed by master that reside
    outside of /etc/origin/master as masters will now run as pods and cannot
    utilize files outside of that path as they will not be mounted inside the
    containers."""
    def run(self, tmp=None, task_vars=None):
        """Run this action module"""
        result = super(ActionModule, self).run(tmp, task_vars)

        # self.task_vars holds all in-scope variables.
        # Ignore settting self.task_vars outside of init.
        # pylint: disable=W0201
        self.task_vars = task_vars or {}

        # mastercfg should be a dictionary from scraping an existing master's
        # config yaml file.
        mastercfg = self._task.args.get('mastercfg')

        # We migrate some paths for users automatically, so we pop those.
        pop_migrated_fields(mastercfg)

        # Create an empty list to append strings from our config file to to check
        # later.
        strings_to_check = []

        walk_mapping(mastercfg, strings_to_check)

        check_strings(strings_to_check)

        result["changed"] = False
        result["failed"] = False
        result["msg"] = "Aight, configs looking good"
        return result
