# (c) cavaliba.com - tests / ipam / subnet occupancy computation

from django.test import TestCase, override_settings

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


def occ(subnet):
    """compute_subnet_occupancy() takes the target's cidr/network/child-networks directly
    (decoupled from IpamSubnet, so the Subnet list view can reuse it against the cached index
    without a per-row Instance lookup) - this wraps an IpamSubnet for test convenience."""
    child_networks = [ref.network for ref in subnet.child_subnet]
    return compute_subnet_occupancy(subnet.subnet, subnet.subobj, child_networks)


class SubnetOccupancyTest(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",
                },
            },
        )
        helper.add_schema(
            classname="occtest",
            field_definition={
                "ip_address": {"dataformat": "ipv4", "displayname": "IP"},
            },
        )
        helper.add_schema(
            classname="ipam_ip",
            options={"keyname_mode": "edit"},
            field_definition={
                "description": {"dataformat": "string", "displayname": "Description"},
            },
        )

        helper.add_instance(
            classname="ipam_subnet",
            keyname="10.9.0.0/24",
            fields={"subnet": "10.9.0.0/24", "description": "clinic"},
        )
        helper.add_instance(
            classname="ipam_subnet",
            keyname="10.9.0.64/28",
            fields={"subnet": "10.9.0.64/28", "description": "server vlan"},
        )

        # two hosts directly in the /24 (not in the /28 child)
        helper.add_instance(
            classname="occtest", keyname="host1", fields={"ip_address": "10.9.0.10"}
        )
        helper.add_instance(
            classname="occtest", keyname="host2", fields={"ip_address": "10.9.0.11"}
        )

        # one host inside the /28 child - must be excluded from the /24's own occupancy
        helper.add_instance(
            classname="occtest", keyname="host3", fields={"ip_address": "10.9.0.66"}
        )

        # a curated ipam_ip record with no ipv4-format field of its own (keyname is the IP)
        helper.add_instance(
            classname="ipam_ip", keyname="10.9.0.12", fields={"description": "gateway"}
        )

        # noise outside the subnet entirely
        helper.add_instance(
            classname="occtest", keyname="host4", fields={"ip_address": "10.20.0.5"}
        )

    def test_occupancy_counts_direct_hosts_and_ipam_ip_but_excludes_child_subnet(self):
        subnet = IpamSubnet("10.9.0.0/24", set_subnet=True)
        result = occ(subnet)

        self.assertTrue(result["computed"])
        self.assertEqual(
            result["count"], 3
        )  # host1, host2, ipam_ip 10.9.0.12 - not host3 (in child)

        ips = {row["ip"] for row in result["ip_list"]}
        self.assertEqual(ips, {"10.9.0.10", "10.9.0.11", "10.9.0.12"})

    def test_occupancy_percent_is_count_over_size(self):
        subnet = IpamSubnet("10.9.0.0/24", set_subnet=True)
        result = occ(subnet)
        self.assertEqual(result["percent"], round(3 / 256 * 100, 2))

    def test_child_subnet_sees_its_own_host_only(self):
        subnet = IpamSubnet("10.9.0.64/28", set_subnet=True)
        result = occ(subnet)
        self.assertTrue(result["computed"])
        self.assertEqual(result["count"], 1)
        self.assertEqual(result["ip_list"][0]["ip"], "10.9.0.66")

    def test_result_is_cached(self):
        subnet = IpamSubnet("10.9.0.0/24", set_subnet=True)
        first = occ(subnet)

        # add a new host after the first computation - a cached result must not see it
        helper.add_instance(
            classname="occtest", keyname="host5", fields={"ip_address": "10.9.0.20"}
        )

        second = occ(subnet)
        self.assertEqual(first["count"], second["count"])

    @override_settings(CAVALIBA_IPAM_OCCUPANCY_MINPREFIX=20)
    def test_wide_subnet_is_skipped_per_minprefix(self):
        helper.add_instance(
            classname="ipam_subnet",
            keyname="10.9.0.0/16",
            fields={"subnet": "10.9.0.0/16", "description": "region"},
        )
        cache.clear()
        subnet = IpamSubnet("10.9.0.0/16", set_subnet=True)
        result = occ(subnet)
        self.assertFalse(result["computed"])
        self.assertIsNone(result["count"])
        self.assertEqual(result["ip_list"], [])

    @override_settings(CAVALIBA_IPAM_OCCUPANCY_MINPREFIX=20)
    def test_subnet_at_or_above_minprefix_is_computed(self):
        subnet = IpamSubnet("10.9.0.0/24", set_subnet=True)  # /24 >= /20
        result = occ(subnet)
        self.assertTrue(result["computed"])
