interface IPRange {
    start: bigint;
    end: bigint;
}

/**
 * Converts IP address string to BigInt for numerical operations
 */
function ipToBigInt(ip: string): bigint {
    return ip.split('.')
        .reduce((acc, octet) => BigInt.asUintN(64, (acc << BigInt(8)) + BigInt(parseInt(octet))), BigInt(0));
}

/**
 * Converts BigInt to IP address string
 */
function bigIntToIp(num: bigint): string {
    const octets: number[] = [];
    for (let i = 0; i < 4; i++) {
        octets.unshift(Number(num & BigInt(255)));
        num = num >> BigInt(8);
    }
    return octets.join('.');
}

/**
 * Converts CIDR to IP range
 */
function cidrToRange(cidr: string): IPRange {
    const [ip, prefix] = cidr.split('/');
    const prefixBits = parseInt(prefix);
    const ipBigInt = ipToBigInt(ip);
    const mask = BigInt.asUintN(64, (BigInt(1) << BigInt(32 - prefixBits)) - BigInt(1));
    const start = ipBigInt & ~mask;
    const end = start | mask;
    return { start, end };
}

/**
 * Finds the next available CIDR block given existing allocations
 * @param existingCidrs Array of existing CIDR blocks
 * @param blockSize Desired prefix length for the new block (e.g., 24 for /24)
 * @param startCidr Optional CIDR to start searching from (default: "0.0.0.0/0")
 * @returns Next available CIDR block or null if none found
 */
export function findNextAvailableCidr(
    existingCidrs: string[],
    blockSize: number,
    startCidr: string = "0.0.0.0/0"
): string | null {
    // Convert existing CIDRs to ranges and sort them
    const existingRanges = existingCidrs
        .map(cidr => cidrToRange(cidr))
        .sort((a, b) => (a.start < b.start ? -1 : 1));

    // Calculate block size
    const blockSizeBigInt = BigInt(1) << BigInt(32 - blockSize);

    // Start from the beginning of the given CIDR
    let current = cidrToRange(startCidr).start;
    const maxIp = cidrToRange(startCidr).end;

    // Iterate through existing ranges
    for (let i = 0; i <= existingRanges.length; i++) {
        const nextRange = existingRanges[i];

        // Align current to block size
        const alignedCurrent = current + ((blockSizeBigInt - (current % blockSizeBigInt)) % blockSizeBigInt);

        // Check if we've gone beyond the maximum allowed IP
        if (alignedCurrent + blockSizeBigInt - BigInt(1) > maxIp) {
            return null;
        }

        // If we're at the end of existing ranges or found a gap
        if (!nextRange || alignedCurrent + blockSizeBigInt - BigInt(1) < nextRange.start) {
            return `${bigIntToIp(alignedCurrent)}/${blockSize}`;
        }

        // Move current pointer to after the current range
        current = nextRange.end + BigInt(1);
    }

    return null;
}

/**
* Checks if a given IP address is within a CIDR range
* @param ip IP address to check
* @param cidr CIDR range to check against
* @returns boolean indicating if IP is within the CIDR range
*/
export function isIpInCidr(ip: string, cidr: string): boolean {
    const ipBigInt = ipToBigInt(ip);
    const range = cidrToRange(cidr);
    return ipBigInt >= range.start && ipBigInt <= range.end;
}