# (c) cavaliba.com - tests / ipam / subnet hierarchy (parent/child, in-memory index)

from django.test import TestCase

import app_home.cache as cache
from app_ipam.common import IpamIP, IpamSubnet
from tests import helper


class SubnetHierarchyTest(TestCase):
    def setUp(self):
        cache.clear()
        helper.add_schema(
            classname="ipam_subnet",
            options={"keyname_mode": "edit"},
            field_definition={
                "description": {"dataformat": "string", "displayname": "Description"},
                "subnet": {
                    "dataformat": "ipv4",
                    "dataformat_ext": "subnet strict",
                    "displayname": "Subnet",
                },
            },
        )

        # deep nesting with a large gap and no intermediate instance in between,
        # to prove the old maxdepth=3-capped recursion is gone
        helper.add_instance(
            classname="ipam_subnet",
            keyname="10.5.0.0/16",
            fields={"subnet": "10.5.0.0/16", "description": "region"},
        )
        helper.add_instance(
            classname="ipam_subnet",
            keyname="10.5.3.4/30",
            fields={"subnet": "10.5.3.4/30", "description": "deep child"},
        )

    def test_deep_child_found_despite_large_gap(self):
        """10.5.3.4/30 is 14 bit-levels below 10.5.0.0/16 with nothing in between - the old
        recurse_child(maxdepth=3) would miss it, the in-memory index must not."""
        subnet = IpamSubnet("10.5.0.0/16", set_subnet=True)
        child_cidrs = [str(c) for c in subnet.child_subnet]
        self.assertIn("10.5.3.4/30", child_cidrs)

    def test_deep_child_is_reported_as_parent_of_itself_too(self):
        # builtin ipam_subnet data (220-ipam.yml) also seeds 10.0.0.0/8 and 0.0.0.0/0, which are
        # legitimately wider ancestors too - check the nearest (most-specific) one, first in the
        # smallest-to-largest ordering, rather than the exact full list.
        subnet = IpamSubnet("10.5.3.4/30", set_subnet=True)
        parent_cidrs = [str(p) for p in subnet.parent_subnet]
        self.assertEqual(parent_cidrs[0], "10.5.0.0/16")

    def test_first_rank_filtering_with_intermediate_subnet(self):
        """Adding an intermediate /24 between the /16 and the /30: the /16's children should
        now be just the /24 (first rank), not the /30 (which is nested inside the /24)."""
        helper.add_instance(
            classname="ipam_subnet",
            keyname="10.5.3.0/24",
            fields={"subnet": "10.5.3.0/24", "description": "clinic"},
        )
        cache.clear()

        region = IpamSubnet("10.5.0.0/16", set_subnet=True)
        region_children = [str(c) for c in region.child_subnet]
        self.assertEqual(region_children, ["10.5.3.0/24"])

        clinic = IpamSubnet("10.5.3.0/24", set_subnet=True)
        clinic_children = [str(c) for c in clinic.child_subnet]
        self.assertEqual(clinic_children, ["10.5.3.4/30"])

        clinic_parents = [str(p) for p in clinic.parent_subnet]
        self.assertEqual(clinic_parents[0], "10.5.0.0/16")

    def test_ip_inside_deep_child_resolves_to_smallest_subnet(self):
        ip = IpamIP("10.5.3.5", set_subnet=True)
        self.assertEqual(str(ip.subnet), "10.5.3.4/30")
        parent_cidrs = [str(p) for p in ip.parent_subnet]
        self.assertEqual(parent_cidrs[0], "10.5.0.0/16")

    def test_ip_outside_any_specific_subnet_falls_back_to_global_catchall(self):
        """No subnet more specific than the builtin 0.0.0.0/0 catch-all covers this address -
        it should still resolve to that catch-all, not come back empty."""
        ip = IpamIP("192.0.2.1", set_subnet=True)
        self.assertIsNotNone(ip.subnet)
        self.assertEqual(str(ip.subnet), "0.0.0.0/0")
        self.assertEqual(ip.parent_subnet, [])
