/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

package org.apache.cassandra.cql3;

import java.net.InetAddress;
import java.net.UnknownHostException;

import org.junit.Test;

import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

public class CIDRTest
{
    @Test
    public void testIpv4StartingAndEndingIP()
    {
        CIDR cidr = CIDR.getInstance("255.255.255.25/24");
        assertEquals("255.255.255.0/24", cidr.toString());
        assertEquals("255.255.255.0", cidr.getStartIpAddress().getHostAddress());
        assertEquals("255.255.255.255", cidr.getEndIpAddress().getHostAddress());

        cidr = CIDR.getInstance("255.12.143.255/21");
        assertEquals("255.12.136.0/21", cidr.toString());
        assertEquals("255.12.136.0", cidr.getStartIpAddress().getHostAddress());
        assertEquals("255.12.143.255", cidr.getEndIpAddress().getHostAddress());

        cidr = CIDR.getInstance("255.25.55.255/31");
        assertEquals("255.25.55.254/31", cidr.toString());
        assertEquals("255.25.55.254", cidr.getStartIpAddress().getHostAddress());
        assertEquals("255.25.55.255", cidr.getEndIpAddress().getHostAddress());

        cidr = CIDR.getInstance("255.25.55.255/32");
        assertEquals("255.25.55.255/32", cidr.toString());
        assertEquals("255.25.55.255", cidr.getStartIpAddress().getHostAddress());
        assertEquals("255.25.55.255", cidr.getEndIpAddress().getHostAddress());
    }

    @Test
    public void testIpv6AdjustedToStartingIP()
    {
        CIDR cidr = CIDR.getInstance("2222:3333:4444:5555:6666:7777:8888:9999/16");
        assertEquals("2222:0:0:0:0:0:0:0/16", cidr.toString());
        assertEquals("2222:0:0:0:0:0:0:0", cidr.getStartIpAddress().getHostAddress());
        assertEquals("2222:ffff:ffff:ffff:ffff:ffff:ffff:ffff", cidr.getEndIpAddress().getHostAddress());

        cidr = CIDR.getInstance("2222:3333:4444:5555::/24");
        assertEquals("2222:3300:0:0:0:0:0:0/24", cidr.toString());
        assertEquals("2222:3300:0:0:0:0:0:0", cidr.getStartIpAddress().getHostAddress());
        assertEquals("2222:33ff:ffff:ffff:ffff:ffff:ffff:ffff", cidr.getEndIpAddress().getHostAddress());

        cidr = CIDR.getInstance("2222:3333::4444:550f/127");
        assertEquals("2222:3333:0:0:0:0:4444:550e/127", cidr.toString());
        assertEquals("2222:3333:0:0:0:0:4444:550e", cidr.getStartIpAddress().getHostAddress());
        assertEquals("2222:3333:0:0:0:0:4444:550f", cidr.getEndIpAddress().getHostAddress());
    }

    @Test
    public void testIpv4MappedIpv6()
    {
        CIDR cidr = CIDR.getInstance("::ffff:152.153.154.155/128");
        assertEquals("152.153.154.155/32", cidr.toString());
        assertEquals(32, cidr.getNetMask());
        assertFalse(cidr.isIPv6());
        assertTrue(cidr.isIPv4());
        assertTrue(cidr.equals(CIDR.getInstance("152.153.154.155/32")));

        cidr = CIDR.getInstance("::ffff:152.153.154.155/120");
        assertEquals("152.153.154.0/24", cidr.toString());
        assertEquals(24, cidr.getNetMask());
        assertFalse(cidr.isIPv6());
        assertTrue(cidr.isIPv4());
        assertTrue(cidr.equals(CIDR.getInstance("152.153.154.0/24")));
    }

    @Test()
    public void testCidrWithInvalidNetMask()
    {
        assertThatThrownBy(() -> CIDR.getInstance("100.100.100.100/33"))
        .isInstanceOf(IllegalArgumentException.class)
        .hasMessageContaining("100.100.100.100/33 is not a valid CIDR String");

        assertThatThrownBy(() -> new CIDR(InetAddress.getByName("100.100.100.100"), (short)33))
        .isInstanceOf(IllegalArgumentException.class)
        .hasMessageContaining("Invalid netmask 33 for IP 100.100.100.100");
    }

    @Test()
    public void testInvalidIpv4MappedIpv6Mask()
    {
        assertThatThrownBy((() -> CIDR.getInstance("::ffff:152.153.154.155/90").toString()))
        .isInstanceOf(IllegalArgumentException.class)
        .hasMessageContaining("::ffff:152.153.154.155/90 is not a valid CIDR String");
    }

    @Test
    public void testNullOrEmptyCidrString()
    {
        assertThatThrownBy((() -> CIDR.getInstance(null).toString()))
        .isInstanceOf(IllegalArgumentException.class)
        .hasMessageContaining("null is not a valid CIDR String");

        assertThatThrownBy((() -> CIDR.getInstance("").toString()))
        .isInstanceOf(IllegalArgumentException.class)
        .hasMessageContaining(" is not a valid CIDR String");
    }

    @Test
    public void testCidrStringWithoutNetMask()
    {
        assertThatThrownBy((() -> CIDR.getInstance("10.20.30.40").toString()))
        .isInstanceOf(IllegalArgumentException.class)
        .hasMessageContaining("10.20.30.40 is not a valid CIDR String");
    }

    @Test
    public void testCidrStringWithInvalidIp()
    {
        assertThatThrownBy((() -> CIDR.getInstance("10.20.300.40/24").toString()))
        .isInstanceOf(IllegalArgumentException.class)
        .hasMessageContaining("10.20.300.40/24 is not a valid CIDR String");
    }

    @Test
    public void testCidrAsTupleString()
    {
        CIDR cidr = CIDR.getInstance("10.20.30.40/24");
        assertEquals("('10.20.30.0', 24)", cidr.asCqlTupleString());
    }

    @Test
    public void testCompareIPs() throws UnknownHostException
    {
        assertTrue(CIDR.compareIPs(InetAddress.getByName("10.20.30.40"), InetAddress.getByName("10.20.30.41")) < 0);
        assertTrue(CIDR.compareIPs(InetAddress.getByName("10.20.30.40"), InetAddress.getByName("10.20.30.39")) > 0);
        assertTrue(CIDR.compareIPs(InetAddress.getByName("10.20.30.40"), InetAddress.getByName("10.20.30.40")) == 0);

        assertTrue(CIDR.compareIPs(InetAddress.getByName("2222:3333::4444:550f"),
                                   InetAddress.getByName("2222:5555::4444:550f")) < 0);
        assertTrue(CIDR.compareIPs(InetAddress.getByName("2222:ffff:ffff:ffff:ffff:ffff:ffff:ffff"),
                                   InetAddress.getByName("2222:3333::4444:550f")) > 0);
        assertTrue(CIDR.compareIPs(InetAddress.getByName("2222:3333::4444:550f"),
                                   InetAddress.getByName("2222:3333:0000:0000::4444:550f")) == 0);

        assertTrue(CIDR.compareIPs(InetAddress.getByName("10.20.30.40"), InetAddress.getByName("10.22.40.40")) < 0);
        assertTrue(CIDR.compareIPs(InetAddress.getByName("10.24.40.40"), InetAddress.getByName("10.20.30.40")) > 0);

        assertTrue(CIDR.compareIPs(InetAddress.getByName("0.20.30.40"), InetAddress.getByName("255.20.40.40")) < 0);
        assertTrue(CIDR.compareIPs(InetAddress.getByName("255.20.40.40"), InetAddress.getByName("128.20.30.40")) > 0);

        assertTrue(CIDR.compareIPs(InetAddress.getByName("::ffff:152.153.154.155"),
                                   InetAddress.getByName("152.154.154.155")) < 0);
        assertTrue(CIDR.compareIPs(InetAddress.getByName("::ffff:152.154.154.155"),
                                   InetAddress.getByName("152.153.154.155")) > 0);
        assertTrue(CIDR.compareIPs(InetAddress.getByName("::ffff:152.153.154.155"),
                                   InetAddress.getByName("152.153.154.155")) == 0);
    }

    @Test
    public void testCidrOverlaps()
    {
        assertFalse(CIDR.overlaps(CIDR.getInstance("10.20.30.0/24"), CIDR.getInstance("20.20.30.0/24")));
        assertFalse(CIDR.overlaps(CIDR.getInstance("10.20.63.0/24"), CIDR.getInstance("10.20.79.0/24")));

        assertTrue(CIDR.overlaps(CIDR.getInstance("10.20.30.0/24"), CIDR.getInstance("10.20.30.0/25")));
        assertTrue(CIDR.overlaps(CIDR.getInstance("10.20.30.0/24"), CIDR.getInstance("10.20.30.0/24")));

        assertTrue(CIDR.overlaps(CIDR.getInstance("::ffff:152.153.154.155/120"),
                                 CIDR.getInstance("152.153.154.155/24")));
        assertFalse(CIDR.overlaps(CIDR.getInstance("::ffff:152.153.154.155/120"),
                                  CIDR.getInstance("::ffff:152.153.153.155/120")));

        assertTrue(CIDR.overlaps(CIDR.getInstance("2222:3333:4444:5555:6666:7777:8888:9999/16"),
                                 CIDR.getInstance("2222:3333:4444:5555:6666:7777:8888:9999/36")));
        assertFalse(CIDR.overlaps(CIDR.getInstance("2222:3333:4444:5555:6666:7777:8888:9999/16"),
                                  CIDR.getInstance("1010:3333:4444:5555:6666:7777:8888:9999/16")));
    }
}
