# Copyright 2015 Internap.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import unittest

from hamcrest import assert_that, equal_to, is_, instance_of
import mock
from ncclient.operations import RPCError
from netaddr import IPAddress
from flexmock import flexmock, flexmock_teardown

from netman.core.objects.interface_states import OFF, ON
from tests import ExactIpNetwork, ignore_deprecation_warnings
from tests.api import open_fixture
from netman.adapters.switches.remote import RemoteSwitch, factory
from netman.core.objects.access_groups import IN, OUT
from netman.core.objects.exceptions import UnknownBond, VlanAlreadyExist, BadBondLinkSpeed, LockedSwitch, \
    NetmanException, UnknownInterface, UnknownSession, UnknownVlan
from netman.core.objects.port_modes import ACCESS, TRUNK, DYNAMIC
from netman.core.objects.switch_descriptor import SwitchDescriptor


class AnException(Exception):
    pass


@ignore_deprecation_warnings
def test_factory():
    switch = factory(SwitchDescriptor(hostname='hostname', model='juniper', username='username', password='password', port=22))

    assert_that(switch, instance_of(RemoteSwitch))
    assert_that(switch.switch_descriptor.hostname, equal_to("hostname"))
    assert_that(switch.switch_descriptor.model, equal_to("juniper"))
    assert_that(switch.switch_descriptor.username, equal_to("username"))
    assert_that(switch.switch_descriptor.password, equal_to("password"))
    assert_that(switch.switch_descriptor.port, equal_to(22))


class RemoteSwitchTest(unittest.TestCase):
    netman_url = 'http://netman.example.org:1234'

    def setUp(self):
        self.switch = RemoteSwitch(SwitchDescriptor(
            model="juniper", hostname="toto", username="tutu",
            password="titi", port=1234, netman_server=self.netman_url))

        self.requests_mock = flexmock()
        self.switch.requests = self.requests_mock
        self.headers = {
            'Netman-Port': "1234",
            'Netman-Model': 'juniper',
            'Netman-Password': 'titi',
            'Netman-Username': 'tutu',
            'Netman-Max-Version': "2",
            'Netman-Verbose-Errors': 'yes',
        }

    def tearDown(self):
        flexmock_teardown()

    def test_switch_has_a_logger_configured_with_the_switch_name(self):
        assert_that(self.switch.logger.name, is_(RemoteSwitch.__module__ + ".toto"))

    @mock.patch('uuid.uuid4')
    def test_start_then_commit_returns_to_normal_behavior(self, m_uuid):
        m_uuid.return_value = '0123456789'
        self.headers['Netman-Session-Id'] = '0123456789'
        self.requests_mock.should_receive("post").once().with_args(
            url=self.netman_url+'/switches-sessions/0123456789',
            headers=self.headers,
            data=JsonData(hostname="toto")
        ).and_return(
            Reply(
                content=json.dumps({'session_id': '0123456789'}),
                status_code=201))
        self.requests_mock.should_receive("post").once().with_args(
            url=self.netman_url+'/switches-sessions/0123456789/actions',
            data='start_transaction',
            headers={'Netman-Verbose-Errors': "yes",
                     'Netman-Max-Version': "2",
                     'Netman-Session-Id': '0123456789'}
        ).and_return(
            Reply(
                content="",
                status_code=204))
        self.requests_mock.should_receive("post").once().with_args(
            url=self.netman_url+'/switches-sessions/0123456789/actions',
            data='commit',
            headers={'Netman-Verbose-Errors': "yes",
                     'Netman-Max-Version': "2",
                     'Netman-Session-Id': '0123456789'}
        ).and_return(
            Reply(
                content="",
                status_code=204))
        self.requests_mock.should_receive("post").once().with_args(
            url=self.netman_url+'/switches-sessions/0123456789/actions',
            data='end_transaction',
            headers={'Netman-Verbose-Errors': "yes",
                     'Netman-Max-Version': "2",
                     'Netman-Session-Id': '0123456789'}
        ).and_return(
            Reply(
                content="",
                status_code=204))
        self.requests_mock.should_receive("delete").once().with_args(
            url=self.netman_url+'/switches-sessions/0123456789',
            headers={'Netman-Verbose-Errors': "yes",
                     'Netman-Max-Version': "2",
                     'Netman-Session-Id': '0123456789'}
        ).and_return(
            Reply(
                content="",
                status_code=204))

        self.switch.connect()
        self.switch.start_transaction()
        self.switch.commit_transaction()
        self.switch.end_transaction()
        self.switch.disconnect()
        self.setUp()
        self.test_add_bond()

    @mock.patch('uuid.uuid4')
    def test_connect_fails_to_obtain_a_session(self, m_uuid):
        m_uuid.return_value = '0123456789'
        self.headers['Netman-Session-Id'] = '0123456789'
        self.requests_mock.should_receive("post").once().with_args(
            url=self.netman_url+'/switches-sessions/0123456789',
            headers=self.headers,
            data=JsonData(hostname="toto")
        ).and_return(
            Reply(
                content=json.dumps({
                    "error": "",
                    "error-module": AnException.__module__,
                    "error-class": AnException.__name__
                }),
                status_code=500))

        with self.assertRaises(AnException):
            self.switch.connect()

    @mock.patch('uuid.uuid4')
    def test_disconnect_fails_and_return_to_normal_behavior(self, m_uuid):
        m_uuid.return_value = '0123456789'
        self.headers['Netman-Session-Id'] = '0123456789'
        self.requests_mock.should_receive("post").once().with_args(
            url=self.netman_url+'/switches-sessions/0123456789',
            headers=self.headers,
            data=JsonData(hostname="toto")
        ).and_return(
            Reply(
                content=json.dumps({'session_id': '0123456789'}),
                status_code=201))

        self.requests_mock.should_receive("delete").once().with_args(
            url=self.netman_url+'/switches-sessions/0123456789',
            headers={'Netman-Verbose-Errors': "yes",
                     'Netman-Max-Version': "2",
                     'Netman-Session-Id': '0123456789'}
        ).and_return(
            Reply(
                content=json.dumps({
                    "error": "",
                    "error-module": AnException.__module__,
                    "error-class": AnException.__name__
                }),
                status_code=500))

        self.switch.connect()
        with self.assertRaises(AnException):
            self.switch.disconnect()
        self.setUp()
        self.test_add_bond()

    @mock.patch('uuid.uuid4')
    def test_session_is_used_when_we_are_in_a_session(self, m_uuid):
        m_uuid.return_value = '0123456789'
        self.headers['Netman-Session-Id'] = '0123456789'
        self.requests_mock.should_receive("post").once().with_args(
            url=self.netman_url+'/switches-sessions/0123456789',
            headers=self.headers,
            data=JsonData(hostname="toto")
        ).and_return(
            Reply(
                content=json.dumps({'session_id': '0123456789'}),
                status_code=201))

        self.requests_mock.should_receive("post").once().with_args(
            url=self.netman_url+'/switches-sessions/0123456789/bonds',
            headers=self.headers,
            data=JsonData(number=6)
        ).and_return(
            Reply(
                content='',
                status_code=201))

        self.switch.connect()
        self.switch.add_bond(6)

    @mock.patch('uuid.uuid4')
    def test_commit_transaction_fails_to_commit(self, m_uuid):
        m_uuid.return_value = '0123456789'

        self.headers['Netman-Session-Id'] = '0123456789'