"""
tests of a few worker utilities
"""
import ipaddress
import json
import re

import jsonschema

from inventory_provider.tasks import worker
from inventory_provider.tasks import common
from inventory_provider.routes import msr


def backend_db():
    return common._get_redis({
        'redis': {
            'hostname': None,
            'port': None
        },
        'redis-databases': [0, 7]
    }).db


def test_build_subnet_db(mocked_worker_module):
    """
    Verify that valid reverse subnet objects are created.

    :param mocked_worker_module: fixture
    """

    address_schema = {
        '$schema': 'http://json-schema.org/draft-07/schema#',

        'definitions': {
            'interface': {
                'type': 'object',
                'properties': {
                    'name': {'type': 'string'},
                    'interface address': {'type': 'string'},
                    'interface name': {'type': 'string'},
                    'router': {'type': 'string'}
                },
                'required': ['name', 'interface address',
                             'interface name', 'router'],
                'additionalProperties': False
            }
        },

        'type': 'array',
        'items': {"$ref": "#/definitions/interface"},
    }

    db = backend_db()  # also forces initialization

    def _x(k):
        return k.startswith('subnets:')

    for k in list(filter(_x, db.keys())):
        del db[k]

    worker._build_subnet_db()

    found_record = False
    for key, value in db.items():

        if not _x(key):
            continue

        found_record = True

        m = re.match('^subnets:(.+)', key)
        assert m
        address = m.group(1)

        value = json.loads(value)
        jsonschema.validate(value, address_schema)

        for ifc in value:
            assert ifc['interface address'] == address

    assert found_record


def test_build_juniper_peering_db(mocked_worker_module):
    """
    Verify that valid juniper peering db objects are created.

    :param mocked_worker_module: fixture
    """

    # same as inventory_provider.juniper.PEERING_LIST_SCHEMA,
    # but with "hostname" in every returned record

    LOGICAL_SYSTEM_PEERING_SCHEMA = {
        "type": "object",
        "properties": {
            "logical-system": {"type": "string"},
            "group": {"type": "string"},
            "description": {"type": "string"},
            "address": {"type": "string"},
            "remote-asn": {"type": "integer"},
            "local-asn": {"type": "integer"},
            "hostname": {"type": "string"}
        },
        # local/remote-asn and/or description are not always present,
        # just based on empirical tests - not a problem
        "required": ["logical-system", "group", "address"],
        "additionalProperties": False
    }

    TOP_LEVEL_PEERING_SCHEMA = {
        "type": "object",
        "properties": {
            "group": {"type": "string"},
            "description": {"type": "string"},
            "address": {"type": "string"},
            "remote-asn": {"type": "integer"},
            "local-asn": {"type": "integer"},
            "hostname": {"type": "string"}
        },
        # lots of internal peerings - so maybe no explicit asn's
        "required": ["group", "address"],
        "additionalProperties": False
    }

    INSTANCE_PEERING = {
        "type": "object",
        "properties": {
            "instance": {"type": "string"},
            "group": {"type": "string"},
            "description": {"type": "string"},
            "address": {"type": "string"},
            "remote-asn": {"type": "integer"},
            "local-asn": {"type": "integer"},
            "hostname": {"type": "string"}
        },
        # description and-or local-asn is not always present,
        # just based on empirical tests - not a problem
        "required": ["instance", "group", "address", "remote-asn"],
        "additionalProperties": False
    }

    DETAILED_PEERING_LIST_SCHEMA = {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
            "top-level-peering": TOP_LEVEL_PEERING_SCHEMA,
            "instance-peering": INSTANCE_PEERING,
            "logical-system-peering": LOGICAL_SYSTEM_PEERING_SCHEMA,
            "peering": {
                "oneOf": [
                    {"$ref": "#/definitions/top-level-peering"},
                    {"$ref": "#/definitions/instance-peering"},
                    {"$ref": "#/definitions/logical-system-peering"}
                ]
            }
        },
        "type": "array",
        "items": {"$ref": "#/definitions/peering"}
    }

    db = backend_db()  # also forces initialization

    # remove the juniper-peerings:* items that
    # will be created by _build_juniper_peering_db
    def _x(k):
        if not k.startswith('juniper-peerings'):
            return False
        if k.startswith('juniper-peerings:hosts:'):
            return False
        return True

    for k in list(filter(_x, db.keys())):
        del db[k]

    worker._build_juniper_peering_db()

    found_record = False
    found_logical_system = False
    found_group = False
    for key, value in db.items():

        if not _x(key):
            continue

        value = json.loads(value)
        assert value

        found_record = True

        if key.startswith('juniper-peerings:ix-groups:'):
            for address in value:
                canonical = ipaddress.ip_interface(address).ip.exploded
                assert address == canonical
            continue

        jsonschema.validate(value, DETAILED_PEERING_LIST_SCHEMA)

        if 'logical-system:' in key:
            jsonschema.validate(value, msr.PEERING_LIST_SCHEMA)
            m = re.match(r'.*logical-system:(.+)$', key)
            assert all(p['logical-system'] == m.group(1) for p in value)
            found_logical_system = True

        if 'group:' in key:
            jsonschema.validate(value, msr.PEERING_LIST_SCHEMA)
            m = re.match(r'.*group:(.+)$', key)
            assert all(p['group'] == m.group(1) for p in value)
            found_group = True

    assert found_record
    assert found_logical_system
    assert found_group


def test_build_snmp_peering_db(mocked_worker_module):
    """
    Verify that valid snmp peering db objects are .

    :param mocked_worker_module: fixture
    """
    peering_list_schema = {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
            "peering": {
                "type": "object",
                "properties": {
                    "local": {"type": "string"},
                    "remote": {"type": "string"},
                    "oid": {"type": "string"},
                    "community": {"type": "string"},
                    "hostname": {"type": "string"}
                },
                "required": ["local", "remote",
                             "oid", "community",
                             "hostname"],
                "additionalProperties": False
            }
        },
        "type": "array",
        "items": {"$ref": "#/definitions/peering"}
    }

    db = backend_db()  # also forces initialization

    def _x(k):
        if not k.startswith('snmp-peerings'):
            return False
        if k.startswith('snmp-peerings:hosts:'):
            return False
        return True

    for k in list(filter(_x, db.keys())):
        del db[k]

    worker._build_snmp_peering_db()

    found_record = False
    for key, value in db.items():

        if not _x(key):
            continue

        value = json.loads(value)
        jsonschema.validate(value, peering_list_schema)
        assert value
        found_record = True

    assert found_record


def test_update_interfaces_to_services(mocker, mocked_worker_module):
    test_data = [
        {'equipment': 'eqa', 'interface_name': 'ifca', 'x': 99},
        {'equipment': 'eqb', 'interface_name': 'ifcb', 'z': {'a': 1}}
    ]

    get_circuits = mocker.patch('inventory_provider.db.opsdb.get_circuits')
    get_circuits.return_value = test_data

    db = backend_db()  # also forces initialization
    db.clear()

    worker.update_interfaces_to_services()

    for service in test_data:
        key = 'opsdb:interface_services:{equipment}:{interface_name}'.format(
            **service)
        cached_service = json.loads(db[key])
        assert cached_service == [service]