# Copyright (c) 2015 OpenStack Foundation
#
#    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 copy

import netaddr
from neutron_lib import constants as lib_constants
from neutron_lib.services.qos import constants as qos_consts
from oslo_utils import uuidutils

from neutron.common import ipv6_utils

_uuid = uuidutils.generate_uuid


class FakeDev:
    def __init__(self, name):
        self.name = name


def get_ha_interface(ip='169.254.192.1', mac='12:34:56:78:2b:5d'):
    subnet_id = _uuid()
    return {'admin_state_up': True,
            'device_id': _uuid(),
            'device_owner': lib_constants.DEVICE_OWNER_ROUTER_HA_INTF,
            'fixed_ips': [{'ip_address': ip,
                           'prefixlen': 18,
                           'subnet_id': subnet_id}],
            'id': _uuid(),
            'mac_address': mac,
            'name': 'L3 HA Admin port 0',
            'mtu': 1500,
            'network_id': _uuid(),
            'status': 'ACTIVE',
            'subnets': [{'cidr': '169.254.192.0/18',
                         'gateway_ip': '169.254.255.254',
                         'id': subnet_id}],
            'tenant_id': '',
            'agent_id': _uuid(),
            'agent_host': 'aaa',
            'priority': 1}


def prepare_router_data(ip_version=lib_constants.IP_VERSION_4,
                        enable_snat=None, num_internal_ports=1,
                        enable_floating_ip=False, enable_ha=False,
                        extra_routes=False, dual_stack=False, enable_gw=True,
                        v6_ext_gw_with_sub=True,
                        snat_bound_fip=False,
                        enable_pf_floating_ip=False,
                        vrrp_id=None,
                        **kwargs):
    fixed_ips = []
    subnets = []
    gateway_mac = kwargs.get('gateway_mac', 'ca:fe:de:ad:be:ee')
    extra_subnets = []
    for loop_version in (lib_constants.IP_VERSION_4,
                         lib_constants.IP_VERSION_6):
        if (loop_version == lib_constants.IP_VERSION_4 and
                (ip_version == lib_constants.IP_VERSION_4 or dual_stack)):
            ip_address = kwargs.get('ip_address', '19.4.4.4')
            prefixlen = 24
            subnet_cidr = kwargs.get('subnet_cidr', '19.4.4.0/24')
            gateway_ip = kwargs.get('gateway_ip', '19.4.4.1')
            _extra_subnet = {'cidr': '9.4.5.0/24'}
        elif (loop_version == lib_constants.IP_VERSION_6 and
              (ip_version == lib_constants.IP_VERSION_6 or dual_stack) and
              v6_ext_gw_with_sub):
            ip_address = kwargs.get('ip_address', 'fd00::4')
            prefixlen = 64
            subnet_cidr = kwargs.get('subnet_cidr', 'fd00::/64')
            gateway_ip = kwargs.get('gateway_ip', 'fd00::1')
            _extra_subnet = {'cidr': 'fd01::/64'}
        else:
            continue
        subnet_id = _uuid()
        fixed_ips.append({'ip_address': ip_address,
                          'subnet_id': subnet_id,
                          'prefixlen': prefixlen})
        subnets.append({'id': subnet_id,
                        'cidr': subnet_cidr,
                        'gateway_ip': gateway_ip})
        extra_subnets.append(_extra_subnet)
    if not fixed_ips and v6_ext_gw_with_sub:
        raise ValueError("Invalid ip_version: %s" % ip_version)

    router_id = _uuid()
    ex_gw_port = {}
    if enable_gw:
        ex_gw_port = {'id': _uuid(),
                      'mac_address': gateway_mac,
                      'mtu': 1500,
                      'network_id': _uuid(),
                      'fixed_ips': fixed_ips,
                      'subnets': subnets,
                      'extra_subnets': extra_subnets}

    external_gateway_info = {"qos_policy_id": kwargs.get('qos_policy_id')}

    routes = []
    if extra_routes:
        routes = [{'destination': '8.8.8.0/24', 'nexthop': '19.4.4.4'}]

    router = {
        'id': router_id,
        'distributed': False,
        lib_constants.INTERFACE_KEY: [],
        'routes': routes,
        'gw_port': ex_gw_port,
        'external_gateway_info': external_gateway_info}

    router_fips = router.get(lib_constants.FLOATINGIP_KEY, [])
    if enable_floating_ip:
        fip = {'id': _uuid(),
               'port_id': _uuid(),
               'status': 'DOWN',
               'floating_ip_address': '19.4.4.2',
               'fixed_ip_address': '10.0.0.1'}
        qos_policy_id = kwargs.get(qos_consts.QOS_POLICY_ID)
        if qos_policy_id:
            fip[qos_consts.QOS_POLICY_ID] = qos_policy_id
        router_fips.append(fip)

    if snat_bound_fip:
        fip = {'id': _uuid(),
               'port_id': _uuid(),
               'status': 'DOWN',
               'floating_ip_address': '19.4.4.3',
               'fixed_ip_address': '10.0.0.2'}
        qos_policy_id = kwargs.get(qos_consts.QOS_POLICY_ID)
        if qos_policy_id:
            fip[qos_consts.QOS_POLICY_ID] = qos_policy_id
        router_fips.append(fip)
    router[lib_constants.FLOATINGIP_KEY] = router_fips

    pf_fips = []
    if enable_pf_floating_ip:
        fip = {'id': _uuid(),
               'port_id': _uuid(),
               'status': 'DOWN',
               'floating_ip_address': '19.4.4.4',
               'fixed_ip_address': '10.0.0.3'}
        qos_policy_id = kwargs.get(qos_consts.QOS_POLICY_ID)
        if qos_policy_id:
            fip[qos_consts.QOS_POLICY_ID] = qos_policy_id
        pf_fips.append(fip)
    router['_pf_floatingips'] = pf_fips

    router_append_interface(router, count=num_internal_ports,
                            ip_version=ip_version, dual_stack=dual_stack)
    if enable_ha:
        ha_port_ip = kwargs.get('ha_port_ip', '169.254.192.1')
        ha_port_mac = kwargs.get('ha_port_mac', '12:34:56:78:2b:aa')
        router['ha'] = True
        router['ha_vr_id'] = vrrp_id or 1
        router[lib_constants.HA_INTERFACE_KEY] = (
            get_ha_interface(ip=ha_port_ip,
                             mac=ha_port_mac))

    if enable_snat is not None:
        router['enable_snat'] = enable_snat
    return router


def get_subnet_id(port):
    return port['fixed_ips'][0]['subnet_id']


def router_append_interface(router, count=1,
                            ip_version=lib_constants.IP_VERSION_4,
                            ra_mode=None, addr_mode=None, dual_stack=False,
                            same_port=False):
    interfaces = router[lib_constants.INTERFACE_KEY]
    current = sum(netaddr.IPNetwork(subnet['cidr']).version == ip_version
                  for p in interfaces for subnet in p['subnets'])

    # If dual_stack=True, create IPv4 and IPv6 subnets on each port
    # If same_port=True, create ip_version number of subnets on a single port
    # Else create just an ip_version subnet on each port
    if dual_stack:
        ip_versions = [lib_constants.IP_VERSION_4, lib_constants.IP_VERSION_6]
    elif same_port:
        ip_versions = [ip_version] * count
        count = 1
    else:
        ip_versions = [ip_version]

    mac_address = netaddr.EUI('ca:fe:de:ad:be:ef')
    mac_address.dialect = netaddr.mac_unix
    for i in range(current, current + count):
        fixed_ips = []
        subnets = []
        for loop_version in ip_versions:
            if (loop_version == lib_constants.IP_VERSION_4 and
                    (ip_version == lib_constants.IP_VERSION_4 or dual_stack)):
                ip_pool = '35.4.%i.4'
                cidr_pool = '35.4.%i.0/24'
                prefixlen = 24
                gw_pool = '35.4.%i.1'
            elif (loop_version == lib_constants.IP_VERSION_6 and
                    (ip_version == lib_constants.IP_VERSION_6 or dual_stack)):
                ip_pool = 'fd01:%x:1::6'
                cidr_pool = 'fd01:%x:1::/64'
                prefixlen = 64
                gw_pool = 'fd01:%x:1::1'
            else:
                continue
            subnet_id = _uuid()
            fixed_ips.append({'ip_address': ip_pool % i,
                              'subnet_id': subnet_id,
                              'prefixlen': prefixlen})
            subnets.append({'id': subnet_id,
                            'cidr': cidr_pool % i,
                            'gateway_ip': gw_pool % i,
                            'ipv6_ra_mode': ra_mode,
                            'ipv6_address_mode': addr_mode})
        if not fixed_ips:
            raise ValueError("Invalid ip_version: %s" % ip_version)

        interfaces.append(
            {'id': _uuid(),
             'mtu': 1500,
             'network_id': _uuid(),
             'admin_state_up': True,
             'fixed_ips': fixed_ips,
             'mac_address': str(mac_address),
             'subnets': subnets})
        mac_address.value += 1


def router_append_subnet(router, count=1,
                         ip_version=lib_constants.IP_VERSION_4,
                         ipv6_subnet_modes=None, interface_id=None,
                         dns_nameservers=None, network_mtu=0):
    if ip_version == lib_constants.IP_VERSION_6:
        subnet_mode_none = {'ra_mode': None, 'address_mode': None}
        if not ipv6_subnet_modes:
            ipv6_subnet_modes = [subnet_mode_none] * count
        elif len(ipv6_subnet_modes) != count:
            ipv6_subnet_modes.extend([subnet_mode_none for i in
                                      range(len(ipv6_subnet_modes), count)])

    if ip_version == lib_constants.IP_VERSION_4:
        ip_pool = '35.4.%i.4'
        cidr_pool = '35.4.%i.0/24'
        prefixlen = 24
        gw_pool = '35.4.%i.1'
    elif ip_version == lib_constants.IP_VERSION_6:
        ip_pool = 'fd01:%x::6'
        cidr_pool = 'fd01:%x::/64'
        prefixlen = 64
        gw_pool = 'fd01:%x::1'
    else:
        raise ValueError("Invalid ip_version: %s" % ip_version)

    interfaces = copy.deepcopy(router.get(lib_constants.INTERFACE_KEY, []))
    if interface_id:
        try:
            interface = next(i for i in interfaces
                             if i['id'] == interface_id)
        except StopIteration:
            raise ValueError("interface_id not found")

        fixed_ips, subnets = interface['fixed_ips'], interface['subnets']
    else:
        interface = None
        fixed_ips, subnets = [], []

    num_existing_subnets = len(subnets)
    for i in range(count):
        subnet_id = _uuid()
        fixed_ips.append(
                {'ip_address': ip_pool % (i + num_existing_subnets),
                 'subnet_id': subnet_id,
                 'prefixlen': prefixlen})
        subnets.append(
                {'id': subnet_id,
                 'cidr': cidr_pool % (i + num_existing_subnets),
                 'gateway_ip': gw_pool % (i + num_existing_subnets),
                 'dns_nameservers': dns_nameservers,
                 'ipv6_ra_mode': ipv6_subnet_modes[i]['ra_mode'],
                 'ipv6_address_mode': ipv6_subnet_modes[i]['address_mode']})

    if interface:
        # Update old interface
        index = interfaces.index(interface)
        interfaces[index].update({'fixed_ips': fixed_ips, 'subnets': subnets})
    else:
        # New interface appended to interfaces list
        mac_address = netaddr.EUI('ca:fe:de:ad:be:ef')
        mac_address.dialect = netaddr.mac_unix
        interfaces.append(
            {'id': _uuid(),
             'mtu': network_mtu,
             'network_id': _uuid(),
             'admin_state_up': True,
             'mac_address': str(mac_address),
             'fixed_ips': fixed_ips,
             'subnets': subnets})

    router[lib_constants.INTERFACE_KEY] = interfaces


def router_append_pd_enabled_subnet(router, count=1, prefix=None):
    if not prefix:
        prefix = lib_constants.PROVISIONAL_IPV6_PD_PREFIX
    interfaces = router[lib_constants.INTERFACE_KEY]
    current = sum(netaddr.IPNetwork(subnet['cidr']).version == 6
                  for p in interfaces for subnet in p['subnets'])

    mac_address = netaddr.EUI('ca:fe:de:ad:be:ef')
    mac_address.dialect = netaddr.mac_unix
    pd_intfs = []
    for i in range(current, current + count):
        subnet_id = _uuid()
        intf = {'id': _uuid(),
                'mtu': 1500,
                'network_id': _uuid(),
                'admin_state_up': True,
                'fixed_ips': [{'ip_address': '::1',
                               'prefixlen': 64,
                               'subnet_id': subnet_id}],
                'mac_address': str(mac_address),
                'subnets': [{'id': subnet_id,
                             'cidr': prefix,
                             'gateway_ip': '::1',
                             'ipv6_ra_mode': lib_constants.IPV6_SLAAC,
                             'subnetpool_id': lib_constants.IPV6_PD_POOL_ID}]}
        interfaces.append(intf)
        pd_intfs.append(intf)
        mac_address.value += 1


def get_unassigned_pd_interfaces(router):
    pd_intfs = []
    for intf in router[lib_constants.INTERFACE_KEY]:
        for subnet in intf['subnets']:
            if (ipv6_utils.is_ipv6_pd_enabled(subnet) and
                    subnet['cidr'] ==
                    lib_constants.PROVISIONAL_IPV6_PD_PREFIX):
                pd_intfs.append(intf)
    return pd_intfs


def get_assigned_pd_interfaces(router):
    pd_intfs = []
    for intf in router[lib_constants.INTERFACE_KEY]:
        for subnet in intf['subnets']:
            if (ipv6_utils.is_ipv6_pd_enabled(subnet) and
                    subnet['cidr'] !=
                    lib_constants.PROVISIONAL_IPV6_PD_PREFIX):
                pd_intfs.append(intf)
    return pd_intfs


def assign_prefix_for_pd_interfaces(router):
    pd_intfs = []
    for ifno, intf in enumerate(router[lib_constants.INTERFACE_KEY]):
        for subnet in intf['subnets']:
            if (ipv6_utils.is_ipv6_pd_enabled(subnet) and
                    subnet['cidr'] ==
                    lib_constants.PROVISIONAL_IPV6_PD_PREFIX):
                subnet['cidr'] = "2001:db8:%d::/64" % ifno
                pd_intfs.append(intf)
    return pd_intfs


def prepare_ext_gw_test(context, ri, dual_stack=False):
    subnet_id = _uuid()
    fixed_ips = [{'subnet_id': subnet_id,
                  'ip_address': '20.0.0.30',
                  'prefixlen': 24}]
    subnets = [{'id': subnet_id,
                'cidr': '20.0.0.0/24',
                'gateway_ip': '20.0.0.1'}]
    if dual_stack:
        subnet_id_v6 = _uuid()
        fixed_ips.append({'subnet_id': subnet_id_v6,
                          'ip_address': '2001:192:168:100::2',
                          'prefixlen': 64})
        subnets.append({'id': subnet_id_v6,
                        'cidr': '2001:192:168:100::/64',
                        'gateway_ip': '2001:192:168:100::1'})
    ex_gw_port = {'fixed_ips': fixed_ips,
                  'subnets': subnets,
                  'extra_subnets': [{'cidr': '172.16.0.0/24'}],
                  'id': _uuid(),
                  'mtu': 1500,
                  'network_id': _uuid(),
                  'mac_address': 'ca:fe:de:ad:be:ef'}
    interface_name = ri.get_external_device_name(ex_gw_port['id'])

    context.device_exists.return_value = True

    return interface_name, ex_gw_port
