# Copyright 2014 Google Inc. All Rights Reserved.
# Modifications Copyright 2022-2023 Aerleon Project Authors.
#
# 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.

"""Tests for discontinuous subnet mask summarizer."""

import os
import random
import time

from absl import logging
from absl.testing import absltest

from aerleon.lib import nacaddr, summarizer


class SummarizerTest(absltest.TestCase):
    def setUp(self):
        super().setUp()
        random_seed = int(time.time())
        value = os.environ.get('TEST_RANDOM_SEED', '')
        try:
            random_seed = int(value)
        except ValueError:
            pass
        logging.info('Seeding random generator with seed %d', random_seed)
        random.seed(random_seed)

    def testToDottedQuad(self):
        net = summarizer.DSMNet(1 << 32, 4294967264)
        self.assertRaises(ValueError)
        net = summarizer.DSMNet(3232235584, 1 << 16)
        self.assertRaises(ValueError)
        net = summarizer.DSMNet(3232235584, 4294967264)
        self.assertEqual(summarizer.ToDottedQuad(net), ('192.168.0.64', '255.255.255.224'))
        net = summarizer.DSMNet(3232235584, 4294901984)
        self.assertEqual(summarizer.ToDottedQuad(net, negate=True), ('192.168.0.64', '0.0.255.31'))

        test_data = [
            (summarizer.DSMNet(3232235584, 4294967295), True, ('192.168.0.64', '32')),
            (summarizer.DSMNet(3232235584, 4294901760), True, ('192.168.0.64', '16')),
            (summarizer.DSMNet(3232235584, 4294967294), True, ('192.168.0.64', '31')),
            (summarizer.DSMNet(3232235584, 4290772992), True, ('192.168.0.64', '10')),
            (summarizer.DSMNet(3232235584, 4294966016), True, ('192.168.0.64', '255.255.251.0')),
            (summarizer.DSMNet(3232235584, 4294901504), True, ('192.168.0.64', '255.254.255.0')),
        ]

        for net, nondsm, expected in test_data:
            self.assertEqual(summarizer.ToDottedQuad(net, nondsm=nondsm), expected)

    def testInt32ToDottedQuad(self):
        self.assertEqual(summarizer._Int32ToDottedQuad(3232235584), '192.168.0.64')

    def testSummarizeEmptyList(self):
        nets = []
        result = summarizer.Summarize(nets)
        self.assertEqual(result, [])

    def testSummarizeNoNetworks(self):
        nets = []
        for octet in range(0, 256):
            net = nacaddr.IPv4(f"192.{255 - octet!s}.{octet!s}.64/27")
            nets.append(net)
        random.shuffle(nets)
        result = summarizer.Summarize(nets)
        self.assertEqual(len(result), 256)

    def testSummarizeSomeNetworks(self):
        nets = [
            # continiously summarizable to one /25
            nacaddr.IPv4('192.168.0.0/27'),
            nacaddr.IPv4('192.168.0.32/27'),
            nacaddr.IPv4('192.168.0.64/27'),
            nacaddr.IPv4('192.168.0.96/27'),
            # discontiniously summarizable with above
            nacaddr.IPv4('128.168.0.0/25'),
            # not summarizable with above
            nacaddr.IPv4('10.0.0.0/8'),
        ]
        for octet in range(0, 256):
            net = nacaddr.IPv4(f"172.16.{octet!s}.96/30")
            nets.append(net)
        random.shuffle(nets)
        result = summarizer.Summarize(nets)
        self.assertEqual(
            result,
            [
                summarizer.DSMNet(167772160, 4278190080),
                summarizer.DSMNet(2158493696, 3221225344),
                summarizer.DSMNet(2886729824, 4294902012),
            ],
        )

    def testSummarizeAllNetworks(self):
        nets = []
        for octet in range(0, 256):
            net = nacaddr.IPv4(f"192.168.{octet!s}.64/27")
            nets.append(net)
        random.shuffle(nets)
        result = summarizer.Summarize(nets)
        # summarizes to 192.168.0.64 / 255.255.0.224
        self.assertEqual(result, [summarizer.DSMNet(3232235584, 4294901984)])

    def testSummarizeToAllSpace(self):
        nets = [
            nacaddr.IPv4('0.0.0.0/1'),
            nacaddr.IPv4('128.0.0.0/1'),
        ]
        random.shuffle(nets)
        result = summarizer.Summarize(nets)
        self.assertEqual(result, [summarizer.DSMNet(0, 0)])

    def testNacaddrNetToDSMNet(self):
        nacaddr_net = nacaddr.IPv4('192.168.0.64/27')
        dsm_net = summarizer.DSMNet(3232235584, 4294967264, '')
        self.assertEqual(summarizer._NacaddrNetToDSMNet(nacaddr_net), dsm_net)

    def testToPrettyBinaryFormat(self):
        # 192.168.0.64
        self.assertEqual(
            summarizer._ToPrettyBinaryFormat(3232235584), '11000000 10101000 00000000 01000000'
        )
        # 8.8.8.8
        self.assertEqual(
            summarizer._ToPrettyBinaryFormat(134744072), '00001000 00001000 00001000 00001000'
        )
        # 0.0.0.0
        self.assertEqual(
            summarizer._ToPrettyBinaryFormat(0), '00000000 00000000 00000000 00000000'
        )
        # fc00::1
        self.assertEqual(
            summarizer._ToPrettyBinaryFormat(334965454937798799971759379190646833153),
            '11111100 00000000 00000000 00000000 00000000 00000000 00000000 '
            '00000000 00000000 00000000 00000000 00000000 00000000 00000000 '
            '00000000 00000001',
        )

    def testSummarizeDSMONetworks(self):
        fourth_octet = [
            2,
            8,
            20,
            26,
            28,
            32,
            40,
            52,
            58,
            86,
            130,
            136,
            148,
            154,
            156,
            160,
            168,
            180,
            186,
            214,
        ]
        nets = list()

        for octet3 in range(56, 60):
            for octet4 in fourth_octet:
                nets.append(nacaddr.IPv4(f"192.168.{octet3!s}.{octet4!s}/31"))

        result = summarizer.Summarize(nets)
        self.assertEqual(
            result,
            [
                summarizer.DSMNet(3232249858, 4294966398),
                summarizer.DSMNet(3232249864, 4294966366),
                summarizer.DSMNet(3232249876, 4294966390),
                summarizer.DSMNet(3232249882, 4294966366),
                summarizer.DSMNet(3232249888, 4294966398),
                summarizer.DSMNet(3232249908, 4294966398),
                summarizer.DSMNet(3232249942, 4294966398),
            ],
        )

    def testMergeText(self):
        existing_comment = 'comment that already exists'
        addition = 'addition'

        dsm_net = summarizer.DSMNet(167772160, 4278190080)
        self.assertEqual(dsm_net.MergeText(addition), addition)

        dsm_net = summarizer.DSMNet(167772160, 4278190080, existing_comment)
        self.assertEqual(dsm_net.MergeText(existing_comment), existing_comment)

        dsm_net = summarizer.DSMNet(167772160, 4278190080, existing_comment)
        self.assertEqual(dsm_net.MergeText(addition), f"{existing_comment}, {addition}")

    def testOrder(self):
        nets = [
            # not discontinously summarizable with the other two
            nacaddr.IPv4('209.85.147.129/32'),
            # discontinuosly summarizable, but should come before the first one
            nacaddr.IPv4('74.125.20.129/32'),
            nacaddr.IPv4('74.125.21.129/32'),
        ]
        result = summarizer.Summarize(nets)
        self.assertEqual(
            result,
            [summarizer.DSMNet(1249711233, 4294967039), summarizer.DSMNet(3512046465, 4294967295)],
        )


if __name__ == '__main__':
    absltest.main()
