"""Kea config backend testing timers in global params, subnets and shared networks."""

import time
import logging

import pytest

import srv_msg
from cb_model import setup_server_for_config_backend_cmds, get_cfg_default
from dhcp4_scen import get_address, get_rejected


log = logging.getLogger('forge')


pytestmark = [pytest.mark.kea_only,
              pytest.mark.controlchannel,
              pytest.mark.hook,
              pytest.mark.config_backend]


@pytest.mark.v4
@pytest.mark.v6
def test_subnet_and_renew_timer(dhcp_version):
    # change renew timer on different levels (global and subnet)
    # and check if these changes are properly reflected in received ACKs

    cfg = setup_server_for_config_backend_cmds()

    dhcp_key = 'Dhcp%s' % dhcp_version[1]
    subnet_key = 'subnet%s' % dhcp_version[1]

    # define one, default subnet
    _, config = cfg.add_subnet()
    assert 'renew-timer' not in config[dhcp_key][subnet_key][0]

    # check getting address from this subnet
    get_address()

    # change global renew_timer to 1sec and now check
    # if received lease has renew_timer accordingly set
    cfg.set_global_parameter(renew_timer=1)
    get_address(exp_renew_timer=1)

    # change renew_timer on subnet level to 1000sec
    # and now check if received lease has renew_timer accordingly set
    cfg.update_subnet(renew_timer=1000)
    get_address(exp_renew_timer=1000)

    # change again renew_timer on subnet level to 1sec
    # and now check if received lease has renew_timer accordingly set
    cfg.update_subnet(renew_timer=1)
    get_address(exp_renew_timer=1)

    # change again renew_timer on global level to 500sec
    # and now check if it is ignored ans it still should be taken
    # from subnet level
    cfg.set_global_parameter(renew_timer=500)
    get_address(exp_renew_timer=1)


@pytest.mark.v4
@pytest.mark.v6
def test_subnet_and_rebind_timer(dhcp_version):  # pylint: disable=unused-argument
    # change rebind timer on different levels (global and subnet)
    # and check if these changes are properly reflected in received ACKs

    cfg = setup_server_for_config_backend_cmds()

    # define one, default subnet
    cfg.add_subnet()

    # check getting address from this subnet
    get_address()

    # change global renew_timer to 1sec and now check
    # if received lease has renew_timer accordingly set
    cfg.set_global_parameter(rebind_timer=1)
    get_address(exp_rebind_timer=1)

    # change rebind_timer on subnet level to 1000sec
    # and now check if received lease has rebind_timer accordingly set
    cfg.update_subnet(rebind_timer=1000)
    get_address(exp_rebind_timer=1000)

    # change again rebind_timer on subnet level to 1sec
    # and now check if received lease has rebind_timer accordingly set
    cfg.update_subnet(rebind_timer=1)
    get_address(exp_rebind_timer=1)

    # change again rebind_timer on global level to 500sec
    # and now check if it is ignored ans it still should be taken
    # from subnet level
    cfg.set_global_parameter(rebind_timer=500)
    get_address(exp_rebind_timer=1)


@pytest.mark.v4
@pytest.mark.v6
def test_subnet_and_timers_renew_less(dhcp_version):  # pylint: disable=unused-argument
    # change both renew and rebind timers on different levels (global and subnet)
    # and check if these changes are properly reflected in received ACKs
    # in this case renew is always less than rebind time so both should
    # be present in responses

    cfg = setup_server_for_config_backend_cmds()

    # define one, default subnet
    cfg.add_subnet()

    # check getting address from this subnet
    get_address()

    # set renew and rebind timers on global level
    # and check if they are present in ACK packet
    cfg.set_global_parameter(renew_timer=100, rebind_timer=1000)
    get_address(exp_renew_timer=100, exp_rebind_timer=1000)

    # set renew and rebind timers on subnet level
    # and check if they are present in ACK packet
    cfg.update_subnet(renew_timer=200, rebind_timer=2000)
    get_address(exp_renew_timer=200, exp_rebind_timer=2000)

    # change renew and rebind timers on subnet level
    # and check if they are present in ACK packet
    cfg.update_subnet(renew_timer=300, rebind_timer=3000)
    get_address(exp_renew_timer=300, exp_rebind_timer=3000)

    # change renew and rebind timers on global level
    # and check if they are not reflected in ACK packet,
    # they still should be taken from subnet
    cfg.set_global_parameter(renew_timer=400, rebind_timer=4000)
    get_address(exp_renew_timer=300, exp_rebind_timer=3000)


@pytest.mark.v4
@pytest.mark.disabled  # #505 will not be fixed
def test_subnet_and_timers_renew_greater_4():
    # change both renew and rebind timers on different levels (global and subnet)
    # and check that if renew timer is greater than rebind timer
    # then an error is returned

    cfg = setup_server_for_config_backend_cmds()

    # define one, default subnet
    cfg.add_subnet()

    cmd = {'command': 'remote-subnet4-set',
           'arguments': {'remote': {'type': 'mysql'},
                         'server-tags': ['all'],
                         'subnets': [{'id': 1,
                                      'interface': 'enp0s10',
                                      'pools': [{'option-data': [],
                                                 'pool': '192.168.50.1-192.168.50.100'}],
                                      'rebind-timer': 10,
                                      'renew-timer': 100,
                                      'shared-network-name': '',
                                      'subnet': '192.168.50.0/24'}]}}

    response = srv_msg.send_ctrl_cmd(cmd, exp_result=1)

    assert response == {
        "result": 1,
        "text": "subnet configuration failed: the value of renew-timer (100) "
                "is greater than the value of rebind-timer (10)"}

    cmd = {'command': 'remote-global-parameter4-set',
           'arguments': {'parameters': {'rebind-timer': 10, 'renew-timer': 100},
                         'remote': {'type': 'mysql'},
                         'server-tags': ['all']}}

    # bug #505
    response = srv_msg.send_ctrl_cmd(cmd, exp_result=1)

    assert response == {
        "result": 1,
        "text": "subnet configuration failed: the value of renew-timer (100) "
                "is greater than the value of rebind-timer (10)"}


@pytest.mark.v6
@pytest.mark.disabled  # #505 will not be fixed
def test_subnet_and_timers_renew_greater_6():
    # change both renew and rebind timers on different levels (global and subnet)
    # and check that if renew timer is greater than rebind timer
    # then an error is returned

    cfg = setup_server_for_config_backend_cmds()

    # define one, default subnet
    cfg.add_subnet()

    cmd = {'command': 'remote-subnet6-set',
           'arguments': {'remote': {'type': 'mysql'},
                         'server-tags': ['all'],
                         'subnets': [{'id': 1,
                                      'interface': 'enp0s10',
                                      'pools': [{'option-data': [],
                                                 'pool': '2001:db8:1::1-2001:db8:1::100'}],
                                      'rebind-timer': 10,
                                      'renew-timer': 100,
                                      'shared-network-name': '',
                                      'subnet': '2001:db8:1::/64'}]}}

    response = srv_msg.send_ctrl_cmd(cmd, exp_result=1)

    assert response == {
        "result": 1,
        "text": "subnet configuration failed: the value of renew-timer (100) "
                "is greater than the value of rebind-timer (10)"}

    cmd = {'command': 'remote-global-parameter6-set',
           'arguments': {'parameters': {'rebind-timer': 10, 'renew-timer': 100},
                         'remote': {'type': 'mysql'},
                         'server-tags': ['all']}}

    # bug #505
    response = srv_msg.send_ctrl_cmd(cmd, exp_result=1)

    assert response == {
        "result": 1,
        "text": "subnet configuration failed: the value of renew-timer (100) "
                "is greater than the value of rebind-timer (10)"}


@pytest.mark.v4
@pytest.mark.v6
def test_subnet_and_timers_equal(dhcp_version):
    # change both renew and rebind timers on different levels (global and subnet)
    # and check if these changes are properly reflected in received ACKs
    # in this case renew is always equal to rebind time,
    # ie. renew should be ignored and not present in responses

    cfg = setup_server_for_config_backend_cmds()

    # define one, default subnet
    cfg.add_subnet()

    # check getting address from this subnet
    get_address()

    # set renew and rebind timers on global level
    # and as renew equals rebind check if only rebind is present in ACK packet
    cfg.set_global_parameter(renew_timer=1, rebind_timer=1)
    get_address(exp_renew_timer='missing' if dhcp_version == 'v4' else 0, exp_rebind_timer=1)

    # set renew and rebind timers on subnet level
    # and as renew equals rebind check if only rebind is present in ACK packet
    cfg.update_subnet(renew_timer=1000, rebind_timer=1000)
    get_address(exp_renew_timer='missing' if dhcp_version == 'v4' else 0, exp_rebind_timer=1000)

    # change renew and rebind timers on subnet level
    # and as renew equals rebind check if only rebind is present in ACK packet
    cfg.update_subnet(renew_timer=2, rebind_timer=2)
    get_address(exp_renew_timer='missing' if dhcp_version == 'v4' else 0, exp_rebind_timer=2)

    # change renew and rebind timers on global level
    # and check if they are not reflected in ACK packet,
    # they still should be taken from subnet
    cfg.set_global_parameter(renew_timer=5, rebind_timer=5)
    get_address(exp_renew_timer='missing' if dhcp_version == 'v4' else 0, exp_rebind_timer=2)


@pytest.mark.v4
@pytest.mark.v6
def test_subnet_and_timers_mix(dhcp_version):
    # change both renew and rebind timers on different levels (global and subnet)
    # and check if these changes are properly reflected in received ACKs
    # in this case they are in different relations to each other

    cfg = setup_server_for_config_backend_cmds()

    # define one, default subnet
    cfg.add_subnet()

    # check getting address from this subnet
    get_address()

    # change renew and rebind timers that they are less or equal to each other, do it on global level
    cfg.set_global_parameter(renew_timer=1500, rebind_timer=1500)
    get_address(exp_renew_timer='missing' if dhcp_version == 'v4' else 0, exp_rebind_timer=1500)

    cfg.set_global_parameter(renew_timer=1000, rebind_timer=1500)
    get_address(exp_renew_timer=1000, exp_rebind_timer=1500)

    cfg.set_global_parameter(renew_timer=1000, rebind_timer=1000)
    get_address(exp_renew_timer='missing' if dhcp_version == 'v4' else 0, exp_rebind_timer=1000)

    # now change on subnet level in all directions
    cfg.update_subnet(renew_timer=1500, rebind_timer=1500)
    get_address(exp_renew_timer='missing' if dhcp_version == 'v4' else 0, exp_rebind_timer=1500)

    cfg.update_subnet(renew_timer=1000, rebind_timer=1500)
    get_address(exp_renew_timer=1000, exp_rebind_timer=1500)

    cfg.update_subnet(renew_timer=1000, rebind_timer=1000)
    get_address(exp_renew_timer='missing' if dhcp_version == 'v4' else 0, exp_rebind_timer=1000)


@pytest.mark.v4
@pytest.mark.v6
def test_shared_networks_and_timers_renew_less(dhcp_version):  # pylint: disable=unused-argument
    # change both renew and rebind timers on different levels (global, shared network and subnet)
    # and check if these changes are properly reflected in received ACKs
    # in this case renew is always less than rebind time so both should be present in responses

    cfg = setup_server_for_config_backend_cmds()

    # define a shared network with one subnet
    network_cfg, _ = cfg.add_network()
    subnet_cfg, _ = cfg.add_subnet(network=network_cfg)

    # check getting address from this subnet
    get_address()

    # set renew and rebind timers on global level
    # and check if they are present in ACK packet
    cfg.set_global_parameter(renew_timer=10, rebind_timer=100)
    get_address(exp_renew_timer=10, exp_rebind_timer=100)

    # set renew and rebind timers on network level
    # and check if they are present in ACK packet
    network_cfg.update(renew_timer=20, rebind_timer=200)
    get_address(exp_renew_timer=20, exp_rebind_timer=200)

    # change renew and rebind timers on global level
    # and check if they are not reflected in ACK packet,
    # they still should be taken from subnet
    cfg.set_global_parameter(renew_timer=30, rebind_timer=300)
    get_address(exp_renew_timer=20, exp_rebind_timer=200)

    # change renew and rebind timers on network level
    # and check if they are present in ACK packet
    network_cfg.update(renew_timer=40, rebind_timer=400)
    get_address(exp_renew_timer=40, exp_rebind_timer=400)

    # set renew and rebind timers on subnet level
    # and check if they are present in ACK packet
    subnet_cfg.update(renew_timer=50, rebind_timer=500)
    get_address(exp_renew_timer=50, exp_rebind_timer=500)

    # change renew and rebind timers on subnet level
    # and check if they are present in ACK packet
    subnet_cfg.update(renew_timer=60, rebind_timer=600)
    get_address(exp_renew_timer=60, exp_rebind_timer=600)

    # change renew and rebind timers on global level
    # and check if they are not reflected in ACK packet,
    # they still should be taken from subnet
    network_cfg.update(renew_timer=70, rebind_timer=700)
    get_address(exp_renew_timer=60, exp_rebind_timer=600)

    # change renew and rebind timers on global level
    # and check if they are not reflected in ACK packet,
    # they still should be taken from subnet
    cfg.set_global_parameter(renew_timer=80, rebind_timer=800)
    get_address(exp_renew_timer=60, exp_rebind_timer=600)


@pytest.mark.v4
@pytest.mark.v6
def test_subnet_and_valid_lifetime(dhcp_version):
    # change valid-lifetime on different levels (global and subnet)
    # and check if behavior is as expected ie leases after lifetime
    # are available for other clients

    cfg = setup_server_for_config_backend_cmds()

    # define one, default subnet with 1 IP address
    cfg.add_subnet(pool="192.168.50.2/32" if dhcp_version == 'v4' else '2001:db8:1::2/128')

    # check getting address from this subnet by client 1
    get_address(mac_addr='00:00:00:00:00:01', exp_addr='192.168.50.2' if dhcp_version == 'v4' else '2001:db8:1::2')
    # after 2 seconds check if another client 2 can get address - as default lifetime is big
    # it should fail because there is no more IP addresses (there is only 1 taken)
    time.sleep(2)
    get_rejected(mac_addr='00:00:00:00:00:02')

    # change lease lifetime on global level to be small ie. 1sec
    # and 1) extend address pool by 1 IP for new client 3 as previous IP address is taken for long time
    # and 2) check getting address by this new client 3
    cfg.set_global_parameter(valid_lifetime=1)
    cfg.update_subnet(pool="192.168.50.2/31" if dhcp_version == 'v4' else '2001:db8:1::2/127')
    get_address(mac_addr='00:00:00:00:00:03', exp_lease_time=1,
                exp_addr='192.168.50.3' if dhcp_version == 'v4' else '2001:db8:1::3')
    # as lease time is 1 sec after 2secs this just taken IP address should
    # be available for other clients ie. client 4
    time.sleep(2)
    get_address(mac_addr='00:00:00:00:00:04',
                exp_addr='192.168.50.3' if dhcp_version == 'v4' else '2001:db8:1::3')
    # wait for lease expiration for next test steps
    time.sleep(2)

    # change lease lifetime on subnet level to be big ie. 1000sec
    # and check getting address by client 5
    cfg.update_subnet(valid_lifetime=1000)
    get_address(mac_addr='00:00:00:00:00:05', exp_lease_time=1000,
                exp_addr='192.168.50.3' if dhcp_version == 'v4' else '2001:db8:1::3')
    # after 2 seconds check if another client 6 can get address - as new lifetime is big
    # it should fail because there is no more IP addresses (there is only 2 taken)
    time.sleep(2)
    get_rejected(mac_addr='00:00:00:00:00:06')

    # change lease lifetime on subnet level to be small ie. 1sec
    # and check getting address by client 7 but first extent pool by one address
    # as previous IP addresses are taken for long time
    cfg.update_subnet(valid_lifetime=1,
                      pool="192.168.50.2-192.168.50.4" if dhcp_version == 'v4' else '2001:db8:1::2-2001:db8:1::4')
    get_address(mac_addr='00:00:00:00:00:07', exp_lease_time=1,
                exp_addr='192.168.50.4' if dhcp_version == 'v4' else '2001:db8:1::4')
    # as lease time is 1 sec after 2secs this just taken IP address should
    # be available for other clients ie. client 8
    time.sleep(2)
    get_address(mac_addr='00:00:00:00:00:08', exp_addr='192.168.50.4' if dhcp_version == 'v4' else '2001:db8:1::4')


@pytest.mark.v4
@pytest.mark.v6
def test_shared_networks_and_valid_lifetime(dhcp_version):
    # change valid-lifetime on different levels (global, shared network and subnet)
    # and check if behavior is as expected ie leases after lifetime
    # are not available for rebinding

    cfg = setup_server_for_config_backend_cmds()

    # define a shared network with one subnet
    network_cfg, _ = cfg.add_network()
    subnet_cfg, _ = cfg.add_subnet(network=network_cfg,
                                   pool="192.168.50.2/32" if dhcp_version == 'v4' else '2001:db8:1::2/128')

    # check getting address from this subnet by client 1
    get_address(mac_addr='00:00:00:00:00:01', exp_addr='192.168.50.2' if dhcp_version == 'v4' else '2001:db8:1::2')
    # after 2 seconds check if another client 2 can get address - as default lifetime is big
    # it should fail because there is no more IP addresses (there is only 1 that is taken)
    time.sleep(2)
    get_rejected(mac_addr='00:00:00:00:00:02')

    # change lease lifetime on global level to be small ie. 1sec
    # and 1) extend address pool by 1 IP for new client 3 as previous IP address is taken for long time
    # and 2) check getting address by this new client 3
    cfg.set_global_parameter(valid_lifetime=1)
    subnet_cfg.update(pool="192.168.50.2/31" if dhcp_version == 'v4' else '2001:db8:1::2/127')
    get_address(mac_addr='00:00:00:00:00:03', exp_lease_time=1,
                exp_addr='192.168.50.3' if dhcp_version == 'v4' else '2001:db8:1::3')
    # as lease time is 1 sec after 2secs this just taken IP address should
    # be available for other clients ie. client 4
    time.sleep(2)
    get_address(mac_addr='00:00:00:00:00:04', exp_addr='192.168.50.3' if dhcp_version == 'v4' else '2001:db8:1::3')
    # wait for lease expiration for next test steps
    time.sleep(2)

    # change lease lifetime on network level to be big ie. 1000sec
    # and check getting address by client 5
    network_cfg.update(valid_lifetime=1000)
    get_address(mac_addr='00:00:00:00:00:05',
                exp_lease_time=1000, exp_addr='192.168.50.3' if dhcp_version == 'v4' else '2001:db8:1::3')
    # after 2 seconds check if another client 6 can get address - as new lifetime is big
    # it should fail because there is no more IP addresses (there are only 2 that are taken)
    time.sleep(2)
    get_rejected(mac_addr='00:00:00:00:00:06')

    # change lease lifetime on network level to be small ie. 1sec
    # and check getting address by client 7 but first extent pool by one address
    # as previous IP addresses are taken for long time
    network_cfg.update(valid_lifetime=1)
    subnet_cfg.update(pool="192.168.50.2-192.168.50.4" if dhcp_version == 'v4' else '2001:db8:1::2-2001:db8:1::4')
    get_address(mac_addr='00:00:00:00:00:07', exp_lease_time=1,
                exp_addr='192.168.50.4' if dhcp_version == 'v4' else '2001:db8:1::4')
    # as lease time is 1 sec after 2secs this just taken IP address should
    # be available for other clients ie. client 8
    time.sleep(2)
    get_address(mac_addr='00:00:00:00:00:08', exp_addr='192.168.50.4' if dhcp_version == 'v4' else '2001:db8:1::4')
    # wait for lease expiration for next test steps
    time.sleep(2)

    # change lease lifetime on subnet level to be big ie. 1000sec
    # and check getting address by client 9
    subnet_cfg.update(valid_lifetime=1000)
    get_address(mac_addr='00:00:00:00:00:09', exp_lease_time=1000,
                exp_addr='192.168.50.4' if dhcp_version == 'v4' else '2001:db8:1::4')
    # after 2 seconds check if another client 10 can get address - as new lifetime is big
    # it should fail because there is no more IP addresses (there are only 4 that are taken)
    time.sleep(2)
    get_rejected(mac_addr='00:00:00:00:00:10')

    # change lease lifetime on subnet level to be small ie. 1sec
    # and check getting address by client 11 but first extent pool by one address
    # as previous IP addresses are taken for long time
    subnet_cfg.update(valid_lifetime=1,