import requests
import re
import json
import time

# API endpoint and file paths
url = "https://bitnodes.io/api/v1/snapshots/latest/"
previous_file = "knownknots.txt"
banlist_file = "banlist.json"

# Timestamps
ban_created = int(time.time())
banned_until = int(time.time()) + (100 * 365 * 24 * 60 * 60)  # 100 years

def strip_port(address):
    # Remove port from IPv4, IPv6, and onion addresses
    if address.startswith('['):
        # IPv6
        return re.sub(r'^\[(.*)\]:\d+$', r'\1', address)
    else:
        # IPv4, onion, or i2p
        return address.split(':')[0]

def get_address_type(address):
    # Categorize address by type
    if re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', address):
        return 'IPv4'
    elif re.match(r'^[0-9a-fA-F:]+$', address):
        return 'IPv6'
    elif address.endswith('.onion'):
        return 'onion'
    elif address.endswith('.i2p'):
        return 'i2p'
    return 'unknown'

# Fetch data from API
try:
    response = requests.get(url)
    response.raise_for_status()
    data = response.json()
except requests.RequestException as e:
    print(f"Failed to fetch data from API: {e}")
    exit(1)

# Extract nodes
nodes = data.get("nodes", {})

# Get current Knots addresses
current_addresses = set()
for address, details in nodes.items():
    user_agent = details[1]
    if "/Knots" in user_agent:
        clean_address = strip_port(address)
        current_addresses.add(clean_address)

# Read previous addresses
previous_addresses = set()
try:
    with open(previous_file, "r", encoding="utf-8") as f:
        previous_addresses = {line.strip() for line in f if line.strip()}
except FileNotFoundError:
    print(f"Previous file {previous_file} not found. Using only current addresses.")

# Calculate new addresses
new_addresses = current_addresses - previous_addresses

# Combine current and previous addresses, removing duplicates
all_addresses = current_addresses | previous_addresses

# Categorize and sort addresses
ipv4_addresses = []
ipv6_addresses = []
onion_addresses = []
i2p_addresses = []
unknown_addresses = []

for address in all_addresses:
    addr_type = get_address_type(address)
    if addr_type == 'IPv4':
        ipv4_addresses.append(address)
    elif addr_type == 'IPv6':
        ipv6_addresses.append(address)
    elif addr_type == 'onion':
        onion_addresses.append(address)
    elif addr_type == 'i2p':
        i2p_addresses.append(address)
    else:
        unknown_addresses.append(address)

# Sort each category alphabetically
ipv4_addresses.sort()
ipv6_addresses.sort()
onion_addresses.sort()
i2p_addresses.sort()
unknown_addresses.sort()

# Create banlist entries
banlist = []
for address in ipv4_addresses + ipv6_addresses + onion_addresses + i2p_addresses + unknown_addresses:
    addr_type = get_address_type(address)
    # Append /32 for IPv4 and /128 for IPv6
    if addr_type == 'IPv4':
        formatted_address = f"{address}/32"
    elif addr_type == 'IPv6':
        formatted_address = f"{address}/128"
    else:
        formatted_address = address  # Leave onion, i2p, and unknown unchanged
    
    entry = {
        "version": 1,
        "ban_created": ban_created,
        "banned_until": banned_until,
        "address": formatted_address
    }
    banlist.append(entry)

# Create JSON structure with warning
output_data = {
    "_warning_": "This file is automatically generated and updated by Bitcoin Core. Please do not edit this file while the node is running, as any changes might be ignored or overwritten.",
    "banned_nets": banlist
}

# Write to banlist.json
with open(banlist_file, "w", encoding="utf-8") as f:
    json.dump(output_data, f, indent=4)

# Update knownknots.txt with new addresses
with open(previous_file, "w", encoding="utf-8") as f:
    for address in ipv4_addresses + ipv6_addresses + onion_addresses + i2p_addresses + unknown_addresses:
        f.write(f"{address}\n")

# Print summary
print(f"Banlist written to {banlist_file}")
print(f"Updated previous addresses in {previous_file}")
print(f"Total unique addresses: {len(all_addresses)}")
print(f"New addresses added: {len(new_addresses)}")
