#!/usr/bin/env python3

import requests
import json
from urllib.parse import urljoin

class NetBoxClient:
    def __init__(self, url, token):
        self.base_url = url.rstrip('/')
        self.api_url = urljoin(self.base_url + '/', 'api/')
        self.headers = {
            'Authorization': f'Token {token}',
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        }
        self.session = requests.Session()
        self.session.headers.update(self.headers)
    
    def get(self, endpoint, params=None):
        """Make GET request to NetBox API"""
        url = urljoin(self.api_url, endpoint.lstrip('/'))
        response = self.session.get(url, params=params)
        response.raise_for_status()
        return response.json()
    
    def get_all(self, endpoint, params=None):
        """Get all results handling pagination"""
        if params is None:
            params = {}
        params['limit'] = 100
        
        all_results = []
        url = urljoin(self.api_url, endpoint.lstrip('/'))
        
        while url:
            response = self.session.get(url, params=params)
            response.raise_for_status()
            data = response.json()
            
            all_results.extend(data['results'])
            url = data['next']
            params = None  # Don't send params on subsequent requests
            
        return all_results
    
    # Site methods
    def get_sites(self, **kwargs):
        return self.get_all('dcim/sites/', params=kwargs)
    
    def get_site_by_name(self, name):
        sites = self.get('dcim/sites/', params={'name': name})
        if sites['count'] > 0:
            return sites['results'][0]
        return None
    
    # Prefix methods
    def get_prefixes(self, **kwargs):
        return self.get_all('ipam/prefixes/', params=kwargs)
    
    def get_prefixes_by_site(self, site_name):
        """Get all prefixes associated with a site"""
        site = self.get_site_by_name(site_name)
        if not site:
            return []
        return self.get_all('ipam/prefixes/', params={'site_id': site['id']})
    
    # IP Address methods
    def get_ip_addresses(self, **kwargs):
        return self.get_all('ipam/ip-addresses/', params=kwargs)
    
    def get_ip_addresses_by_site(self, site_name):
        """Get all IP addresses associated with a site"""
        site = self.get_site_by_name(site_name)
        if not site:
            return []
        
        # First try by site_id
        ips = self.get_all('ipam/ip-addresses/', params={'site_id': site['id']})
        
        # Also get IPs assigned to devices at this site
        devices = self.get_all('dcim/devices/', params={'site_id': site['id']})
        for device in devices:
            device_ips = self.get_all('ipam/ip-addresses/', params={'device_id': device['id']})
            ips.extend(device_ips)
        
        # Remove duplicates
        seen = set()
        unique_ips = []
        for ip in ips:
            if ip['id'] not in seen:
                seen.add(ip['id'])
                unique_ips.append(ip)
        
        return unique_ips
    
    # Device methods
    def get_devices_by_site(self, site_name):
        """Get all devices at a site"""
        site = self.get_site_by_name(site_name)
        if not site:
            return []
        return self.get_all('dcim/devices/', params={'site_id': site['id']})
    
    # VLAN methods
    def get_vlans_by_site(self, site_name):
        """Get all VLANs at a site"""
        site = self.get_site_by_name(site_name)
        if not site:
            return []
        return self.get_all('ipam/vlans/', params={'site_id': site['id']})


def compare_subnets():
    """Compare subnets from router with NetBox"""
    # NetBox connection
    nb = NetBoxClient('https://netbox.vntx.net/', 'e50298f7fd20f7fd6f1931f635511b34f6e8cfde')
    
    # Subnets found on router (from previous scan)
    router_subnets = [
        {'address': '10.250.1.25/29', 'network': '10.250.1.24', 'interface': 'ether3-climax-11ghz'},
        {'address': '10.254.254.101/32', 'network': '10.254.254.101', 'interface': 'loopback'},
        {'address': '100.64.3.254/22', 'network': '100.64.0.0', 'interface': 'verona'},
        {'address': '204.110.188.254/27', 'network': '204.110.188.224', 'interface': 'verona'},
        {'address': '100.64.15.254/22', 'network': '100.64.12.0', 'interface': 'vlan_19_ether6'},
        {'address': '10.10.95.254/20', 'network': '10.10.80.0', 'interface': 'vlan_10_ether6'},
        {'address': '10.10.15.254/20', 'network': '10.10.0.0', 'interface': 'vlan_10_ether6'},
        {'address': '10.0.101.254/24', 'network': '10.0.101.0', 'interface': 'sfp-sfpplus1-verona-tower-switch'},
        {'address': '10.250.1.145/29', 'network': '10.250.1.144', 'interface': 'ether6-switch'},
        {'address': '204.110.191.30/27', 'network': '204.110.191.0', 'interface': 'vlan9_sfpplus1'},
        {'address': '10.25.1.254/24', 'network': '10.25.1.0', 'interface': 'ether1'},
        {'address': '192.168.99.254/24', 'network': '192.168.99.0', 'interface': 'ether10-powerswitch'},
    ]
    
    print("=== Checking Verona Site Subnets in NetBox ===\n")
    
    # Get site info
    site = nb.get_site_by_name('Verona')
    if site:
        print(f"Site: {site['name']} (ID: {site['id']})")
        print(f"Status: {site['status']['label']}")
        print(f"Address: {site['physical_address']}")
        print()
    
    # Get prefixes from NetBox
    print("Fetching NetBox data...")
    prefixes = nb.get_prefixes_by_site('Verona')
    ip_addresses = nb.get_ip_addresses_by_site('Verona')
    vlans = nb.get_vlans_by_site('Verona')
    devices = nb.get_devices_by_site('Verona')
    
    print(f"\nFound in NetBox:")
    print(f"- {len(prefixes)} prefixes")
    print(f"- {len(ip_addresses)} IP addresses")
    print(f"- {len(vlans)} VLANs")
    print(f"- {len(devices)} devices")
    
    # Check if we need to search more broadly
    if len(prefixes) == 0:
        print("\nNo prefixes found with site association. Searching by description/comments...")
        all_prefixes = nb.get_prefixes()
        prefixes = [p for p in all_prefixes if 'verona' in (p.get('description', '') + p.get('comments', '')).lower()]
        print(f"Found {len(prefixes)} prefixes with 'verona' in description/comments")
    
    # Display NetBox prefixes
    if prefixes:
        print("\n=== Prefixes in NetBox ===")
        for prefix in prefixes:
            status = prefix['status']['label'] if prefix.get('status') else 'Unknown'
            role = prefix['role']['name'] if prefix.get('role') else 'None'
            description = prefix.get('description', '')
            print(f"\n{prefix['prefix']}")
            print(f"  Status: {status}")
            print(f"  Role: {role}")
            if description:
                print(f"  Description: {description}")
    
    # Display NetBox IP addresses
    if ip_addresses:
        print("\n\n=== IP Addresses in NetBox ===")
        for ip in ip_addresses:
            status = ip['status']['label'] if ip.get('status') else 'Unknown'
            role = ip['role']['name'] if ip.get('role') else 'None'
            interface = ip.get('assigned_object', {}).get('name', 'Not assigned') if ip.get('assigned_object') else 'Not assigned'
            print(f"\n{ip['address']}")
            print(f"  Status: {status}")
            print(f"  Role: {role}")
            print(f"  Interface: {interface}")
    
    # Compare with router subnets
    print("\n\n=== Comparison Analysis ===")
    
    # Extract prefixes from NetBox data
    netbox_prefixes = set(p['prefix'] for p in prefixes)
    netbox_ips = set(ip['address'] for ip in ip_addresses)
    
    # Check each router subnet
    missing_subnets = []
    missing_ips = []
    
    for subnet in router_subnets:
        # Check if the subnet prefix exists
        subnet_cidr = f"{subnet['network']}/{subnet['address'].split('/')[-1]}"
        
        found_prefix = False
        found_ip = False
        
        # Check against prefixes
        for prefix in netbox_prefixes:
            if subnet_cidr == prefix or subnet['address'] == prefix:
                found_prefix = True
                break
        
        # Check against IP addresses
        if subnet['address'] in netbox_ips:
            found_ip = True
        
        if not found_prefix and not found_ip:
            if '/32' in subnet['address']:
                missing_ips.append(subnet)
            else:
                missing_subnets.append(subnet)
    
    # Report findings
    print(f"\nMissing Subnets (not in NetBox):")
    if missing_subnets:
        for subnet in missing_subnets:
            print(f"  - {subnet['network']}/{subnet['address'].split('/')[-1]} ({subnet['interface']})")
    else:
        print("  None - all subnets are documented")
    
    print(f"\nMissing IP Addresses (not in NetBox):")
    if missing_ips:
        for ip in missing_ips:
            print(f"  - {ip['address']} ({ip['interface']})")
    else:
        print("  None - all IPs are documented")
    
    # Summary
    total_router_subnets = len([s for s in router_subnets if '/32' not in s['address']])
    total_router_ips = len([s for s in router_subnets if '/32' in s['address']])
    
    print(f"\n=== Summary ===")
    print(f"Router has {total_router_subnets} subnets and {total_router_ips} host IPs")
    print(f"NetBox has {len(prefixes)} prefixes and {len(ip_addresses)} IP addresses for Verona")
    print(f"Missing from NetBox: {len(missing_subnets)} subnets and {len(missing_ips)} IPs")
    
    return missing_subnets, missing_ips


if __name__ == "__main__":
    compare_subnets()