#!/usr/bin/env/python3
"""Test pattoo configuration script."""

import os
import getpass
import grp
import unittest
import unittest.mock
import sys
import tempfile
import io
from copy import deepcopy
from collections import defaultdict

import yaml

# Try to create a working PYTHONPATH
EXEC_DIR = os.path.dirname(os.path.realpath(__file__))
ROOT_DIR = os.path.abspath(os.path.join(
    os.path.abspath(os.path.join(
        os.path.abspath(os.path.join(
            EXEC_DIR, os.pardir)), os.pardir)), os.pardir))
_EXPECTED = '''\
{0}pattoo-shared{0}tests{0}pattoo_shared_{0}installation'''.format(os.sep)
if EXEC_DIR.endswith(_EXPECTED) is True:
    # We need to prepend the path in case PattooShared has been installed
    # elsewhere on the system using PIP. This could corrupt expected results
    sys.path.insert(0, ROOT_DIR)
else:
    print('''This script is not installed in the "{0}" directory. Please fix.\
'''.format(_EXPECTED))
    sys.exit(2)

# Pattoo imports
from tests.libraries.configuration import UnittestConfig
from pattoo_shared.installation import configure
from pattoo_shared import files


class TestConfigure(unittest.TestCase):
    """Checks all functions for the Pattoo config script."""

    @classmethod
    def setUpClass(cls):
        """Declare class attributes for Unittesting."""
        # Initialize key variables
        cls._log_directory = tempfile.mkdtemp()
        cls._cache_directory = tempfile.mkdtemp()
        cls._daemon_directory = tempfile.mkdtemp()
        cls._system_daemon_directory = tempfile.mkdtemp()
        cls.default_config = {
            'pattoo': {
                'language': 'en',
                'log_directory': cls._log_directory,
                'log_level': 'debug',
                'cache_directory': cls._cache_directory,
                'daemon_directory': cls._daemon_directory,
                'system_daemon_directory': cls._system_daemon_directory
            },
            'pattoo_agent_api': {
                'ip_address': '127.0.0.1',
                'ip_bind_port': 20201
            },
            'pattoo_web_api': {
                'ip_address': '127.0.0.1',
                'ip_bind_port': 20202,
            }
        }

        cls.updated_config = deepcopy(cls.default_config)
        cls.updated_config['encryption'] = {
            'api_email': 'api_email@example.org'}

        cls.default_server_config = {
            'pattoo_db': {
                'db_pool_size': 10,
                'db_max_overflow': 20,
                'db_hostname': 'localhost',
                'db_username': 'pattoo',
                'db_password': 'password',
                'db_name': 'pattoo'
            },
            'pattoo_api_agentd': {
                'ip_listen_address': '0.0.0.0',
                'ip_bind_port': 20201,
            },
            'pattoo_apid': {
                'ip_listen_address': '0.0.0.0',
                'ip_bind_port': 20202,
            },
            'pattoo_ingesterd': {
                'ingester_interval': 3600,
                'batch_size': 500,
                'graceful_timeout': 10
            }
        }

    def test__merge_config(self):
        """Unittest to test the _merge_config function."""
        # Initialize key variables
        default = {
            'pattoo_api_agentd': {
                'api_encryption_email': 'test_api@example.org',
                'ip_bind_port': 20201,
                'ip_listen_address': '0.0.0.0'},
            'pattoo_apid': {
                'access_token_exp': '15_m',
                'ip_bind_port': 20202,
                'ip_listen_address': '0.0.0.0',
                'jwt_secret_key': '9uBqwWTc-02c2aaK99ULdQ',
                'refresh_token_exp': '1_D'},
            'pattoo_db': {
                'db_hostname': 'localhost',
                'db_max_overflow': 20,
                'db_name': 'pattoo',
                'db_password': 'password',
                'db_pool_size': 10,
                'db_username': 'pattoo'},
            'pattoo_ingesterd': {
                'batch_size': 500,
                'graceful_timeout': 10,
                'ingester_interval': 3600},
            'boo': 'boo'
        }

        modified = {
            'pattoo_api_agentd': {
                'ip_bind_port': 60601,
                'ip_listen_address': '::1'},
            'pattoo_apid': {
                'ip_bind_port': 60602,
                'ip_listen_address': '::3'},
            'pattoo_db': {
                'db_hostname': 'localhost',
                'db_max_overflow': 100,
                'db_name': 'pattoo',
                'db_password': 'n5PaNcV85vR6jmW3',
                'db_pool_size': 10,
                'db_username': 'pattoo'},
            'pattoo_ingesterd': {
                'ingester_interval': 3600,
                'multiprocessing': True}
        }

        expected = {
            'pattoo_api_agentd': {
                'api_encryption_email': 'test_api@example.org',
                'ip_bind_port': 60601,
                'ip_listen_address': '::1'},
            'pattoo_apid': {
                'access_token_exp': '15_m',
                'ip_bind_port': 60602,
                'ip_listen_address': '::3',
                'jwt_secret_key': '9uBqwWTc-02c2aaK99ULdQ',
                'refresh_token_exp': '1_D'},
            'pattoo_db': {
                'db_hostname': 'localhost',
                'db_max_overflow': 100,
                'db_name': 'pattoo',
                'db_password': 'n5PaNcV85vR6jmW3',
                'db_pool_size': 10,
                'db_username': 'pattoo'},
            'pattoo_ingesterd': {
                'batch_size': 500,
                'graceful_timeout': 10,
                'ingester_interval': 3600,
                'multiprocessing': True},
            'boo': 'boo'
            }
        new_expected = expected.copy()

        # Test
        result = configure._merge_config(default, modified)
        self.assertTrue(isinstance(result, dict))
        self.assertFalse(isinstance(result, defaultdict))
        self.assertEqual(sorted(result), sorted(expected))

        # Test with key not found in default
        modified['test'] = 'test'
        new_expected['test'] = 'test'
        result = configure._merge_config(default, modified)
        self.assertTrue(isinstance(result, dict))
        self.assertFalse(isinstance(result, defaultdict))
        self.assertEqual(sorted(result), sorted(new_expected))

    def test_group_exists(self):
        """Unittest to test the group_exists function."""
        # Test case for when the group does not exist
        with self.subTest():
            expected = False

            # Generating random string
            result = configure.group_exists(str(os.urandom(5)))
            self.assertEqual(result, expected)

        # Test case for when the group exists
        with self.subTest():
            expected = True

            # Creating grp.struct object
            grp_struct = grp.getgrgid(os.getgid())
            result = configure.group_exists(grp_struct.gr_name)
            self.assertEqual(result, expected)

    def test_user_exists(self):
        """Unittest to test the user_exists function."""
        # Test case for when the user does not exist
        with self.subTest():
            expected = False

            # Generating random string
            result = configure.user_exists(str(os.urandom(5)))
            self.assertEqual(result, expected)

        # Test case for when the user does exist
        with self.subTest():
            expected = True
            result = configure.user_exists(getpass.getuser())
            self.assertEqual(result, expected)

    def test_read_config(self):
        """Unittest to test the read_server_config function."""
        # Initialize key variables
        expected = self.default_config

        # Create temporary directory using the temp file package
        with tempfile.TemporaryDirectory() as temp_dir:
            file_path = os.path.join(temp_dir, 'pattoo_temp_config.yaml')

            # Delete file if already exists
            if os.path.exists(file_path) is True:
                os.remove(file_path)
            self.assertFalse(os.path.isfile(file_path))

            # Dumps default configuration to file in temp directory
            with open(file_path, 'w+') as temp_config:
                yaml.dump(expected, temp_config, default_flow_style=False)
            result = configure.read_config(file_path, expected)
            self.assertEqual(result, expected)

            # Test updating the configuration
            with self.subTest():
                expected = self.updated_config
                result = configure.read_config(file_path, expected)
                for key, value in result.items():
                    self.assertEqual(expected[key], value)

    def test_pattoo_config_server(self):
        """Test the pattoo_config function for the pattoo server."""
        # Initialize key variables
        expected = '''\
pattoo_api_agentd:
  ip_bind_port: 20201
  ip_listen_address: 0.0.0.0
pattoo_apid:
  ip_bind_port: 20202
  ip_listen_address: 0.0.0.0
pattoo_db:
  db_hostname: localhost
  db_max_overflow: 20
  db_name: pattoo
  db_password: password
  db_pool_size: 10
  db_username: pattoo
pattoo_ingesterd:
  batch_size: 500
  graceful_timeout: 10
  ingester_interval: 3600'''

        # Initialize temporary directory
        with tempfile.TemporaryDirectory() as temp_dir:
            file_path = os.path.join(temp_dir, 'pattoo_server.yaml')

            # Create config file
            configure.pattoo_config(
                'pattoo_server', temp_dir, self.default_server_config)

            with open(file_path, 'r') as temp_config:

                # Remove all whitespace
                result = temp_config.read().strip()
            self.assertEqual(result, expected)

    def test_pattoo_config_default(self):
        """Unittest to test the pattoo_config function with default values."""
        # Initialize key variables
        expected = '''\
pattoo:
  cache_directory: {}
  daemon_directory: {}
  language: en
  log_directory: {}
  log_level: debug
  system_daemon_directory: {}
pattoo_agent_api:
  ip_address: 127.0.0.1
  ip_bind_port: 20201
pattoo_web_api:
  ip_address: 127.0.0.1
  ip_bind_port: 20202
'''.format(
    self._cache_directory, self._daemon_directory,
    self._log_directory, self._system_daemon_directory)

        with tempfile.TemporaryDirectory() as temp_dir:
            file_path = os.path.join(temp_dir, 'pattoo.yaml')

            # Create config file
            configure.pattoo_config('pattoo', temp_dir, self.default_config)

            # Open config file for reading
            with open(file_path, 'r') as temp_config:

                # Remove all whitespace
                result = temp_config.read()
            self.assertEqual(result, expected)

    def test_pattoo_config_custom_dict(self):
        """Test the pattoo_config function with custom dictionary."""
        # Initialize key variables
        prefix = 'pattoo'

        with tempfile.TemporaryDirectory() as temp_dir:
            log_dir = os.path.join(temp_dir, 'pattoo/log')
            cache_dir = os.path.join(temp_dir, 'pattoo/cache')
            daemon_dir = os.path.join(temp_dir, 'pattoo/daemon')
            default = {
                'pattoo': {
                    'log_directory': log_dir,
                    'log_level': 'debug',
                    'language': 'xyz',
                    'cache_directory': cache_dir,
                    'daemon_directory': daemon_dir,
                    'system_daemon_directory': self._system_daemon_directory,
                },
                'pattoo_agent_api': {
                    'ip_address': '127.0.0.6',
                    'ip_bind_port': 50505,
                },
            }

            modified = {
                'dummy_0': {'dummy_1': 'dummy_2', 'dummy_3': 'dummy_4'},
                'encryption': {'api_email': 'modified@example.org'},
                'pattoo_agent_api': {
                    'ip_address': '127.0.0.61', 'ip_bind_port': 50505},
                'pattoo_web_api': {
                    'ip_address': '127.0.0.1', 'ip_bind_port': 30303}
            }

            expected = {
                'dummy_0': {'dummy_1': 'dummy_2', 'dummy_3': 'dummy_4'},
                'encryption': {'api_email': 'modified@example.org'},
                'pattoo': {
                    'cache_directory': cache_dir,
                    'daemon_directory': daemon_dir,
                    'language': 'xyz',
                    'log_directory': log_dir,
                    'log_level': 'debug',
                    'system_daemon_directory': self._system_daemon_directory},
                'pattoo_agent_api': {
                    'ip_address': '127.0.0.61', 'ip_bind_port': 50505},
                'pattoo_web_api': {
                    'ip_address': '127.0.0.1', 'ip_bind_port': 30303}
            }

            file_path = os.path.join(temp_dir, '{}.yaml'.format(prefix))

            # Create config file
            configure.pattoo_config(prefix, temp_dir, default)

            # Test for directories
            result = os.path.isdir(cache_dir)
            with self.subTest():
                self.assertTrue(result)

            result = os.path.isdir(log_dir)
            with self.subTest():
                self.assertTrue(result)

            result = os.path.isdir(daemon_dir)
            with self.subTest():
                self.assertTrue(result)

            # Retrieve config dict from yaml file
            result = configure.read_config(file_path, default)
            with self.subTest():
                self.assertEqual(result, default)

            ########################
            # Test overwriting file
            ########################

            # Delete old version of file
            os.remove(file_path)

            # Create new version of config file
            configure.pattoo_config('pattoo', temp_dir, default)

            # Apply updated configuration to that read from the
            # new configuration file
            result = configure.read_config(file_path, modified)
            with self.subTest():
                self.assertEqual(sorted(result), sorted(expected))

    # Using mock patch to capture output
    @unittest.mock.patch('sys.stdout', new_callable=io.StringIO)
    def test_check_config(self, mock_stdout):
        # Initialize key variables
        """Unittest to test the check_config function."""
        config = {
            'pattoo': {
                'log_directory': self._log_directory,
                'log_level': 'debug',
                'language': 'xyz',
                'cache_directory': self._cache_directory,
                'daemon_directory': self._daemon_directory,
                'system_daemon_directory': self._system_daemon_directory,
            },
            'pattoo_agent_api': {
                'ip_address': '127.0.0.6',
                'ip_bind_port': 50505,
            },
            'pattoo_web_api': {
                'ip_address': '127.0.0.3',
                'ip_bind_port': 30303,
            }
        }

        with tempfile.TemporaryDirectory() as temp_dir:
            config_file = os.path.join(temp_dir, 'pattoo.yaml')

            # Initializing expected output from stdout
            expected = '''\

Configuring {} file.

??: Checking configuration parameters.
OK: Configuration parameter check passed.
'''.format(config_file)

            # Create config file
            configure.pattoo_config('pattoo', temp_dir, config)

            # Run configuration
            configure.check_config(config_file, config)
            self.assertEqual(mock_stdout.getvalue(), expected)

    def test_configure_component(self):
        """Unittest to test the configure_component function."""
        # Initialize key variables
        test_name = str(os.urandom(5))
        expected = {
            'polling_interval': 300,
        }
        with tempfile.TemporaryDirectory() as temp_dir:
            file_path = os.path.join(temp_dir, '{}.yaml'.format(test_name))

            # Create config file
            configure.configure_component(test_name, temp_dir, expected)

            # Retrieve config dict from yaml file
            result = configure.read_config(file_path, expected)
            self.assertEqual(result, expected)


if __name__ == '__main__':
    # Make sure the environment is OK to run unittests
    UnittestConfig().create()

    # Do the unit test
    unittest.main()
