# coding: utf-8

# Python libs
from __future__ import absolute_import

import datetime
import logging

# Salt libs
import salt.beacons.btmp as btmp
from salt.ext import six
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.mock import MagicMock, mock_open, patch

# Salt testing libs
from tests.support.unit import TestCase, skipIf

# pylint: disable=import-error
try:
    import dateutil.parser as dateutil_parser  # pylint: disable=unused-import

    _TIME_SUPPORTED = True
except ImportError:
    _TIME_SUPPORTED = False

raw = b"\x06\x00\x00\x00Nt\x00\x00ssh:notty\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00garet\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00::1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdd\xc7\xc2Y\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
pack = (
    6,
    29774,
    b"ssh:notty\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
    b"\x00\x00\x00\x00",
    b"garet\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
    b"::1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
    0,
    0,
    0,
    1505937373,
    0,
    0,
    0,
    0,
    16777216,
)

log = logging.getLogger(__name__)


class BTMPBeaconTestCase(TestCase, LoaderModuleMockMixin):
    """
    Test case for salt.beacons.[s]
    """

    def setup_loader_modules(self):
        return {btmp: {"__context__": {"btmp.loc": 2}, "__salt__": {}}}

    def test_non_list_config(self):
        config = {}
        ret = btmp.validate(config)

        self.assertEqual(ret, (False, "Configuration for btmp beacon must be a list."))

    def test_empty_config(self):
        config = [{}]

        ret = btmp.validate(config)

        self.assertEqual(ret, (True, "Valid beacon configuration"))

    def test_no_match(self):
        config = [
            {
                "users": {
                    "gareth": {
                        "time_range": {
                            "end": "09-22-2017 5pm",
                            "start": "09-22-2017 3pm",
                        }
                    }
                }
            }
        ]

        ret = btmp.validate(config)

        self.assertEqual(ret, (True, "Valid beacon configuration"))

        with patch("salt.utils.files.fopen", mock_open(b"")) as m_open:
            ret = btmp.beacon(config)
            call_args = next(six.itervalues(m_open.filehandles))[0].call.args
            assert call_args == (btmp.BTMP, "rb"), call_args
            assert ret == [], ret

    def test_invalid_users(self):
        config = [{"users": ["gareth"]}]

        ret = btmp.validate(config)

        self.assertEqual(
            ret, (False, "User configuration for btmp beacon must be a dictionary.")
        )

    def test_invalid_groups(self):
        config = [{"groups": ["docker"]}]

        ret = btmp.validate(config)

        self.assertEqual(
            ret, (False, "Group configuration for btmp beacon must be a dictionary.")
        )

    def test_default_invalid_time_range(self):
        config = [{"defaults": {"time_range": {"start": "3pm"}}}]

        ret = btmp.validate(config)

        self.assertEqual(
            ret,
            (
                False,
                "The time_range parameter for btmp beacon must contain start & end options.",
            ),
        )

    def test_users_invalid_time_range(self):
        config = [{"users": {"gareth": {"time_range": {"start": "3pm"}}}}]

        ret = btmp.validate(config)

        self.assertEqual(
            ret,
            (
                False,
                "The time_range parameter for btmp beacon must contain start & end options.",
            ),
        )

    def test_groups_invalid_time_range(self):
        config = [{"groups": {"docker": {"time_range": {"start": "3pm"}}}}]

        ret = btmp.validate(config)

        self.assertEqual(
            ret,
            (
                False,
                "The time_range parameter for btmp beacon must contain start & end options.",
            ),
        )

    def test_match(self):
        with patch("salt.utils.files.fopen", mock_open(read_data=raw)):
            with patch("struct.unpack", MagicMock(return_value=pack)):
                config = [{"users": {"garet": {}}}]

                ret = btmp.validate(config)

                self.assertEqual(ret, (True, "Valid beacon configuration"))

                _expected = [
                    {
                        "addr": 1505937373,
                        "exit_status": 0,
                        "inittab": "",
                        "hostname": "::1",
                        "PID": 29774,
                        "session": 0,
                        "user": "garet",
                        "time": 0,
                        "line": "ssh:notty",
                        "type": 6,
                    }
                ]
                ret = btmp.beacon(config)
                self.assertEqual(ret, _expected)

    @skipIf(not _TIME_SUPPORTED, "dateutil.parser is missing.")
    def test_match_time(self):
        with patch("salt.utils.files.fopen", mock_open(read_data=raw)):
            mock_now = datetime.datetime(2017, 9, 22, 16, 0, 0, 0)
            with patch("datetime.datetime", MagicMock()), patch(
                "datetime.datetime.now", MagicMock(return_value=mock_now)
            ):
                with patch("struct.unpack", MagicMock(return_value=pack)):
                    config = [
                        {
                            "users": {
                                "garet": {
                                    "time_range": {
                                        "end": "09-22-2017 5pm",
                                        "start": "09-22-2017 3pm",
                                    }
                                }
                            }
                        }
                    ]

                    ret = btmp.validate(config)

                    self.assertEqual(ret, (True, "Valid beacon configuration"))

                    _expected = [
                        {
                            "addr": 1505937373,
                            "exit_status": 0,
                            "inittab": "",
                            "hostname": "::1",
                            "PID": 29774,
                            "session": 0,
                            "user": "garet",
                            "time": 0,
                            "line": "ssh:notty",
                            "type": 6,
                        }
                    ]
                    ret = btmp.beacon(config)
                    self.assertEqual(ret, _expected)

    def test_match_group(self):

        for groupadd in (
            "salt.modules.aix_group",
            "salt.modules.mac_group",
            "salt.modules.pw_group",
            "salt.modules.solaris_group",
            "salt.modules.win_groupadd",
        ):
            mock_group_info = {
                "passwd": "x",
                "gid": 100,
                "name": "users",
                "members": ["garet"],
            }

            with patch("salt.utils.files.fopen", mock_open(read_data=raw)):
                with patch("time.time", MagicMock(return_value=1506121200)):
                    with patch("struct.unpack", MagicMock(return_value=pack)):
                        with patch(
                            "{0}.info".format(groupadd),
                            new=MagicMock(return_value=mock_group_info),
                        ):
                            config = [
                                {
                                    "group": {
                                        "users": {
                                            "time_range": {"end": "5pm", "start": "3pm"}
                                        }
                                    }
                                }
                            ]

                            ret = btmp.validate(config)

                            self.assertEqual(ret, (True, "Valid beacon configuration"))

                            _expected = [
                                {
                                    "addr": 1505937373,
                                    "exit_status": 0,
                                    "inittab": "",
                                    "hostname": "::1",
                                    "PID": 29774,
                                    "session": 0,
                                    "user": "garet",
                                    "time": 0,
                                    "line": "ssh:notty",
                                    "type": 6,
                                }
                            ]
                            ret = btmp.beacon(config)
                            self.assertEqual(ret, _expected)
