package ru.kirillius.pf.sdn.core.Networking;

import lombok.Getter;
import ru.kirillius.json.JSONSerializable;
import ru.kirillius.json.JSONSerializer;
import ru.kirillius.json.SerializationException;
import ru.kirillius.pf.sdn.core.Util.IPv4Util;

import java.util.Objects;
import java.util.regex.Pattern;

/**
 * Immutable representation of an IPv4 subnet with utility helpers for serialization.
 */
@JSONSerializable(IPv4Subnet.Serializer.class)
public class IPv4Subnet {

    /**
     * Serializes subnets to CIDR notation strings for JSON persistence.
     */
    public final static class Serializer implements JSONSerializer<IPv4Subnet> {

        /**
         * Writes the subnet as a string using CIDR notation.
         */
        @Override
        public Object serialize(IPv4Subnet subnet) throws SerializationException {
            return subnet.toString();
        }

        /**
         * Parses a CIDR notation string and returns the corresponding subnet instance.
         */
        @Override
        public IPv4Subnet deserialize(Object o, Class<?> aClass) throws SerializationException {
            return new IPv4Subnet((String) o);
        }
    }

    @Getter
    private final long longAddress;
    @Getter
    private final int prefixLength;


    /**
     * Compares two subnets for equality based on address and prefix length.
     */
    @Override
    public boolean equals(Object o) {
        if (o == null || getClass() != o.getClass()) return false;
        IPv4Subnet that = (IPv4Subnet) o;
        return longAddress == that.longAddress && prefixLength == that.prefixLength;
    }

    /**
     * Computes a hash code using address and prefix length.
     */
    @Override
    public int hashCode() {
        return Objects.hash(longAddress, prefixLength);
    }

    /**
     * Parses a subnet from textual CIDR notation.
     */
    public IPv4Subnet(String subnet) {
        var split = subnet.split(Pattern.quote("/"));
        if (split.length != 2) {
            throw new IllegalArgumentException("Invalid subnet: " + subnet);
        }
        var prefix = Integer.parseInt(split[1]);
        IPv4Util.validatePrefix(prefix);

        longAddress = IPv4Util.ipAddressToLong(split[0]);
        prefixLength = prefix;
    }

    /**
     * Creates a subnet from a numeric address and prefix length.
     */
    public IPv4Subnet(long longAddress, int prefixLength) {
        this.longAddress = longAddress;
        this.prefixLength = prefixLength;
    }

    /**
     * Creates a subnet from a dotted address string and prefix length.
     */
    public IPv4Subnet(String address, int prefixLength) {
        IPv4Util.validatePrefix(prefixLength);

        this.longAddress = IPv4Util.ipAddressToLong(address);
        this.prefixLength = prefixLength;
    }

    /**
     * Calculates the number of addresses within the subnet.
     */
    public long count() {
        return IPv4Util.calculateCountForPrefixLength(prefixLength);
    }

    /**
     * Returns the dotted-decimal representation of the subnet's network address.
     */
    public String getAddress() {
        return IPv4Util.longToIpAddress(longAddress);
    }

    /**
     * Formats the subnet using CIDR notation.
     */
    @Override
    public String toString() {
        return getAddress() + '/' + prefixLength;
    }

    /**
     * Determines whether this subnet overlaps with another.
     */
    public boolean overlaps(IPv4Subnet subnet) {
        var minPrefixLength = Math.min(prefixLength, subnet.prefixLength);
        var commonMask = IPv4Util.calculateMask(minPrefixLength);
        if (minPrefixLength != prefixLength) {
            return false; //can't overlap larger prefix
        }

        return (longAddress & commonMask) == (subnet.longAddress & commonMask);
    }


    public boolean contains(String address){
        var longAddress = IPv4Util.ipAddressToLong(address);
        var commonMask = IPv4Util.calculateMask(prefixLength);
        return (this.longAddress & commonMask) == (longAddress & commonMask);
    }


}
