#!/usr/bin/env python3
"""
IP Whitelist Proxy Server
Proxy HTTP com whitelist de IPs suportando subnets CIDR (ex: 192.168.1.0/24)

Uso:
    python3 ip-whitelist-proxy.py [--port 8888] [--whitelist whitelist.txt]

Configuração da whitelist (um por linha):
    192.168.1.0/24
    10.0.0.0/8
    100.75.88.8
    # Comentários são ignorados
"""

import argparse
import http.server
import ipaddress
import json
import logging
import os
import socketserver
import ssl
import sys
import threading
import urllib.request
import urllib.error
from collections import deque
from datetime import datetime
from pathlib import Path
from typing import Set, Union, Dict, List, Any
from http.client import HTTPResponse
import time
import uuid

# Configuração de logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s | %(levelname)s | %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger('proxy')

# Configurações padrão
DEFAULT_PORT = 8888
DEFAULT_WHITELIST = [
    '127.0.0.1',
    '::1',  # localhost IPv6
    '192.168.1.0/24',
    '10.0.0.0/8',
    '100.64.0.0/10',  # Tailscale CGNAT range
]


class IPWhitelist:
    """Gerencia whitelist de IPs com suporte a subnets CIDR"""

    def __init__(self):
        self.networks: Set[Union[ipaddress.IPv4Network, ipaddress.IPv6Network]] = set()
        self.single_ips: Set[Union[ipaddress.IPv4Address, ipaddress.IPv6Address]] = set()
        self._lock = threading.Lock()

    def add(self, ip_or_network: str) -> bool:
        """Adiciona IP ou subnet à whitelist"""
        ip_or_network = ip_or_network.strip()

        if not ip_or_network or ip_or_network.startswith('#'):
            return False

        try:
            with self._lock:
                if '/' in ip_or_network:
                    # É uma subnet (CIDR)
                    network = ipaddress.ip_network(ip_or_network, strict=False)
                    self.networks.add(network)
                    logger.info(f"Subnet adicionada: {network}")
                else:
                    # É um IP único
                    ip = ipaddress.ip_address(ip_or_network)
                    self.single_ips.add(ip)
                    logger.info(f"IP adicionado: {ip}")
                return True
        except ValueError as e:
            logger.warning(f"IP/subnet inválido ignorado: {ip_or_network} ({e})")
            return False

    def remove(self, ip_or_network: str) -> bool:
        """Remove IP ou subnet da whitelist"""
        try:
            with self._lock:
                if '/' in ip_or_network:
                    network = ipaddress.ip_network(ip_or_network, strict=False)
                    self.networks.discard(network)
                else:
                    ip = ipaddress.ip_address(ip_or_network)
                    self.single_ips.discard(ip)
                return True
        except ValueError:
            return False

    def is_allowed(self, ip_str: str) -> bool:
        """Verifica se um IP está na whitelist"""
        try:
            ip = ipaddress.ip_address(ip_str)

            with self._lock:
                # Verifica IPs únicos
                if ip in self.single_ips:
                    return True

                # Verifica subnets
                for network in self.networks:
                    if ip in network:
                        return True

                return False
        except ValueError:
            logger.warning(f"IP inválido para verificação: {ip_str}")
            return False

    def load_from_file(self, filepath: str) -> int:
        """Carrega whitelist de arquivo (um IP/subnet por linha)"""
        count = 0
        try:
            with open(filepath, 'r') as f:
                for line in f:
                    line = line.strip()
                    if line and not line.startswith('#'):
                        if self.add(line):
                            count += 1
            logger.info(f"Carregados {count} IPs/subnets de {filepath}")
        except FileNotFoundError:
            logger.warning(f"Arquivo não encontrado: {filepath}")
        except Exception as e:
            logger.error(f"Erro ao carregar whitelist: {e}")
        return count

    def load_from_list(self, items: list) -> int:
        """Carrega whitelist de uma lista"""
        count = 0
        for item in items:
            if self.add(item):
                count += 1
        return count

    def get_all(self) -> dict:
        """Retorna todos os IPs e subnets"""
        with self._lock:
            return {
                'single_ips': [str(ip) for ip in self.single_ips],
                'networks': [str(net) for net in self.networks],
                'total': len(self.single_ips) + len(self.networks)
            }

    def __len__(self):
        with self._lock:
            return len(self.single_ips) + len(self.networks)


class RequestHistory:
    """Buffer circular para histórico de requisições com métricas"""

    def __init__(self, max_size: int = 1000):
        self.buffer = deque(maxlen=max_size)
        self.client_stats: Dict[str, Dict] = {}  # {ip: {count, bytes, last_seen}}
        self.target_stats: Dict[str, Dict] = {}  # {domain: {count, success, fail}}
        self.latencies = deque(maxlen=100)  # últimas 100 latências
        self._lock = threading.Lock()

    def add(self, log_entry: Dict) -> None:
        """Adiciona entrada ao histórico"""
        with self._lock:
            self.buffer.append(log_entry)
            self._update_client_stats(log_entry)
            self._update_target_stats(log_entry)
            if 'latency_ms' in log_entry:
                self.latencies.append(log_entry['latency_ms'])

    def _update_client_stats(self, entry: Dict) -> None:
        """Atualiza estatísticas por cliente"""
        ip = entry.get('client_ip', 'unknown')
        if ip not in self.client_stats:
            self.client_stats[ip] = {'count': 0, 'bytes': 0, 'last_seen': None}
        self.client_stats[ip]['count'] += 1
        self.client_stats[ip]['bytes'] += entry.get('bytes_transferred', 0)
        self.client_stats[ip]['last_seen'] = entry.get('timestamp')

    def _update_target_stats(self, entry: Dict) -> None:
        """Atualiza estatísticas por destino"""
        url = entry.get('target_url', '')
        if not url or entry.get('was_blocked'):
            return
        try:
            from urllib.parse import urlparse
            domain = urlparse(url).netloc or 'unknown'
        except:
            domain = 'unknown'

        if domain not in self.target_stats:
            self.target_stats[domain] = {'count': 0, 'success': 0, 'fail': 0}
        self.target_stats[domain]['count'] += 1
        if entry.get('status_code', 0) < 400:
            self.target_stats[domain]['success'] += 1
        else:
            self.target_stats[domain]['fail'] += 1

    def get_history(self, limit: int = 100, offset: int = 0) -> List[Dict]:
        """Retorna histórico paginado (mais recentes primeiro)"""
        with self._lock:
            items = list(self.buffer)
            items.reverse()
            return items[offset:offset + limit]

    def get_stats(self) -> Dict[str, Any]:
        """Retorna estatísticas agregadas"""
        with self._lock:
            # Top clientes
            top_clients = sorted(
                [{'ip': ip, **stats} for ip, stats in self.client_stats.items()],
                key=lambda x: x['count'],
                reverse=True
            )[:10]

            # Top destinos
            top_targets = []
            for domain, stats in self.target_stats.items():
                total = stats['count']
                success_rate = stats['success'] / total if total > 0 else 0
                top_targets.append({
                    'domain': domain,
                    'count': total,
                    'success_rate': round(success_rate, 2)
                })
            top_targets = sorted(top_targets, key=lambda x: x['count'], reverse=True)[:10]

            # Latência média
            avg_latency = sum(self.latencies) / len(self.latencies) if self.latencies else 0

            return {
                'top_clients': top_clients,
                'top_targets': top_targets,
                'average_latency_ms': round(avg_latency, 2),
                'total_logged': len(self.buffer),
                'unique_clients': len(self.client_stats),
                'unique_targets': len(self.target_stats)
            }

    def clear(self) -> None:
        """Limpa todo o histórico"""
        with self._lock:
            self.buffer.clear()
            self.client_stats.clear()
            self.target_stats.clear()
            self.latencies.clear()


# Instância global do histórico
request_history = RequestHistory(max_size=1000)


class ProxyHandler(http.server.BaseHTTPRequestHandler):
    """Handler HTTP com whitelist de IPs"""

    whitelist: IPWhitelist = None
    stats = {
        'requests_total': 0,
        'requests_allowed': 0,
        'requests_blocked': 0,
        'bytes_transferred': 0,
        'start_time': None
    }
    stats_lock = threading.Lock()

    def log_message(self, format, *args):
        """Override para usar nosso logger"""
        client_ip = self.client_address[0]
        logger.info(f"{client_ip} - {format % args}")

    def check_whitelist(self) -> bool:
        """Verifica se o cliente está na whitelist"""
        client_ip = self.client_address[0]

        if self.whitelist and not self.whitelist.is_allowed(client_ip):
            self.send_error_response(403, f"IP {client_ip} não autorizado")
            with self.stats_lock:
                self.stats['requests_blocked'] += 1
            logger.warning(f"BLOQUEADO: {client_ip}")
            return False

        with self.stats_lock:
            self.stats['requests_allowed'] += 1
        return True

    def send_error_response(self, code: int, message: str):
        """Envia resposta de erro JSON"""
        self.send_response(code)
        self.send_header('Content-Type', 'application/json')
        self.send_header('Access-Control-Allow-Origin', '*')
        self.end_headers()

        response = json.dumps({
            'error': True,
            'code': code,
            'message': message,
            'timestamp': datetime.now().isoformat()
        })
        self.wfile.write(response.encode())

    def proxy_request(self, method: str):
        """Faz proxy da requisição"""
        with self.stats_lock:
            self.stats['requests_total'] += 1

        if not self.check_whitelist():
            return

        # Endpoints especiais de gerenciamento
        if self.path == '/_proxy/status':
            self.handle_status()
            return
        elif self.path == '/_proxy/whitelist':
            self.handle_whitelist_list()
            return
        elif self.path == '/_proxy/whitelist/add':
            self.handle_whitelist_add()
            return
        elif self.path == '/_proxy/whitelist/remove':
            self.handle_whitelist_remove()
            return
        elif self.path.startswith('/_proxy/history'):
            self.handle_history()
            return
        elif self.path == '/_proxy/stats':
            self.handle_stats()
            return
        elif self.path.startswith('/_proxy/'):
            self.send_error_response(404, "Endpoint não encontrado")
            return

        # Extrai URL alvo
        target_url = self.path
        if not target_url.startswith(('http://', 'https://')):
            self.send_error_response(400, "URL deve começar com http:// ou https://")
            return

        # Inicia medição de latência
        start_time = time.time()
        client_ip = self.client_address[0]

        try:
            # Prepara headers (remove hop-by-hop headers)
            headers = {}
            hop_by_hop = {'connection', 'keep-alive', 'proxy-authenticate',
                         'proxy-authorization', 'te', 'trailers',
                         'transfer-encoding', 'upgrade', 'host'}

            for key, value in self.headers.items():
                if key.lower() not in hop_by_hop:
                    headers[key] = value

            # Adiciona X-Forwarded-For
            existing_xff = headers.get('X-Forwarded-For', '')
            headers['X-Forwarded-For'] = f"{existing_xff}, {client_ip}".lstrip(', ')

            # Lê body se houver
            content_length = self.headers.get('Content-Length')
            body = None
            if content_length:
                body = self.rfile.read(int(content_length))

            # Faz a requisição
            req = urllib.request.Request(
                target_url,
                data=body,
                headers=headers,
                method=method
            )

            # Configura timeout e SSL
            context = ssl.create_default_context()
            context.check_hostname = False
            context.verify_mode = ssl.CERT_NONE

            with urllib.request.urlopen(req, timeout=30, context=context) as response:
                # Envia resposta
                self.send_response(response.status)

                # Copia headers (exceto hop-by-hop)
                for key, value in response.headers.items():
                    if key.lower() not in hop_by_hop:
                        self.send_header(key, value)

                self.send_header('Access-Control-Allow-Origin', '*')
                self.end_headers()

                # Copia body
                data = response.read()
                self.wfile.write(data)

                bytes_sent = len(data)
                latency_ms = (time.time() - start_time) * 1000

                with self.stats_lock:
                    self.stats['bytes_transferred'] += bytes_sent

                # Registra no histórico
                request_history.add({
                    'id': str(uuid.uuid4()),
                    'timestamp': datetime.now().isoformat(),
                    'client_ip': client_ip,
                    'method': method,
                    'target_url': target_url,
                    'status_code': response.status,
                    'bytes_transferred': bytes_sent,
                    'latency_ms': round(latency_ms, 2),
                    'was_blocked': False
                })

                logger.info(f"PROXY: {method} {target_url} -> {response.status} ({latency_ms:.0f}ms)")

        except urllib.error.HTTPError as e:
            self.send_response(e.code)
            self.send_header('Content-Type', 'text/plain')
            self.end_headers()
            self.wfile.write(f"Erro upstream: {e.code} {e.reason}".encode())
            logger.error(f"HTTP Error: {target_url} -> {e.code}")

        except urllib.error.URLError as e:
            self.send_error_response(502, f"Erro de conexão: {e.reason}")
            logger.error(f"URL Error: {target_url} -> {e.reason}")

        except Exception as e:
            self.send_error_response(500, f"Erro interno: {str(e)}")
            logger.exception(f"Erro ao fazer proxy: {e}")

    def handle_status(self):
        """Retorna status do proxy"""
        with self.stats_lock:
            uptime = 0
            if self.stats['start_time']:
                uptime = (datetime.now() - self.stats['start_time']).total_seconds()

            status = {
                'status': 'running',
                'uptime_seconds': int(uptime),
                'requests': {
                    'total': self.stats['requests_total'],
                    'allowed': self.stats['requests_allowed'],
                    'blocked': self.stats['requests_blocked']
                },
                'bytes_transferred': self.stats['bytes_transferred'],
                'whitelist_count': len(self.whitelist) if self.whitelist else 0
            }

        self.send_response(200)
        self.send_header('Content-Type', 'application/json')
        self.end_headers()
        self.wfile.write(json.dumps(status, indent=2).encode())

    def handle_whitelist_list(self):
        """Lista whitelist atual"""
        if not self.check_whitelist():
            return

        data = self.whitelist.get_all() if self.whitelist else {}

        self.send_response(200)
        self.send_header('Content-Type', 'application/json')
        self.send_header('Access-Control-Allow-Origin', '*')
        self.end_headers()
        self.wfile.write(json.dumps(data, indent=2).encode())

    def handle_whitelist_add(self):
        """Adiciona IP/subnet à whitelist via POST"""
        if not self.check_whitelist():
            return

        try:
            content_length = self.headers.get('Content-Length')
            if not content_length:
                self.send_error_response(400, "Body JSON requerido")
                return

            body = self.rfile.read(int(content_length))
            data = json.loads(body.decode())
            entry = data.get('entry', '').strip()

            if not entry:
                self.send_error_response(400, "Campo 'entry' requerido")
                return

            if self.whitelist.add(entry):
                response = {
                    'success': True,
                    'message': f"Adicionado: {entry}",
                    'whitelist': self.whitelist.get_all()
                }
                self.send_response(200)
            else:
                self.send_error_response(400, f"IP/subnet inválido: {entry}")
                return

        except json.JSONDecodeError:
            self.send_error_response(400, "JSON inválido")
            return
        except Exception as e:
            self.send_error_response(500, str(e))
            return

        self.send_header('Content-Type', 'application/json')
        self.send_header('Access-Control-Allow-Origin', '*')
        self.end_headers()
        self.wfile.write(json.dumps(response, indent=2).encode())

    def handle_whitelist_remove(self):
        """Remove IP/subnet da whitelist via POST"""
        if not self.check_whitelist():
            return

        try:
            content_length = self.headers.get('Content-Length')
            if not content_length:
                self.send_error_response(400, "Body JSON requerido")
                return

            body = self.rfile.read(int(content_length))
            data = json.loads(body.decode())
            entry = data.get('entry', '').strip()

            if not entry:
                self.send_error_response(400, "Campo 'entry' requerido")
                return

            if self.whitelist.remove(entry):
                response = {
                    'success': True,
                    'message': f"Removido: {entry}",
                    'whitelist': self.whitelist.get_all()
                }
                self.send_response(200)
            else:
                self.send_error_response(400, f"Falha ao remover: {entry}")
                return

        except json.JSONDecodeError:
            self.send_error_response(400, "JSON inválido")
            return
        except Exception as e:
            self.send_error_response(500, str(e))
            return

        self.send_header('Content-Type', 'application/json')
        self.send_header('Access-Control-Allow-Origin', '*')
        self.end_headers()
        self.wfile.write(json.dumps(response, indent=2).encode())

    def handle_history(self):
        """Retorna histórico de requisições"""
        if not self.check_whitelist():
            return

        # Parse query params: ?limit=100&offset=0
        from urllib.parse import urlparse, parse_qs
        parsed = urlparse(self.path)
        params = parse_qs(parsed.query)

        limit = int(params.get('limit', [100])[0])
        offset = int(params.get('offset', [0])[0])

        # Limita para evitar abuso
        limit = min(limit, 500)

        history = request_history.get_history(limit=limit, offset=offset)

        response = {
            'status': 'ok',
            'limit': limit,
            'offset': offset,
            'count': len(history),
            'total': request_history.get_stats()['total_logged'],
            'data': history
        }

        self.send_response(200)
        self.send_header('Content-Type', 'application/json')
        self.send_header('Access-Control-Allow-Origin', '*')
        self.end_headers()
        self.wfile.write(json.dumps(response, indent=2).encode())

    def handle_stats(self):
        """Retorna estatísticas agregadas"""
        if not self.check_whitelist():
            return

        stats = request_history.get_stats()

        # Adiciona info do proxy
        with self.stats_lock:
            uptime = 0
            if self.stats['start_time']:
                uptime = (datetime.now() - self.stats['start_time']).total_seconds()

            stats['proxy'] = {
                'uptime_seconds': int(uptime),
                'requests_total': self.stats['requests_total'],
                'requests_allowed': self.stats['requests_allowed'],
                'requests_blocked': self.stats['requests_blocked'],
                'bytes_transferred': self.stats['bytes_transferred']
            }

        self.send_response(200)
        self.send_header('Content-Type', 'application/json')
        self.send_header('Access-Control-Allow-Origin', '*')
        self.end_headers()
        self.wfile.write(json.dumps(stats, indent=2).encode())

    # Handlers para cada método HTTP
    def do_GET(self):
        self.proxy_request('GET')

    def do_POST(self):
        self.proxy_request('POST')

    def do_PUT(self):
        self.proxy_request('PUT')

    def do_DELETE(self):
        self.proxy_request('DELETE')

    def do_PATCH(self):
        self.proxy_request('PATCH')

    def do_HEAD(self):
        self.proxy_request('HEAD')

    def do_OPTIONS(self):
        """CORS preflight"""
        self.send_response(200)
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS')
        self.send_header('Access-Control-Allow-Headers', '*')
        self.send_header('Access-Control-Max-Age', '86400')
        self.end_headers()

    def do_CONNECT(self):
        """HTTPS tunneling (CONNECT method)"""
        with self.stats_lock:
            self.stats['requests_total'] += 1

        if not self.check_whitelist():
            return

        # Parse host:port
        try:
            if ':' in self.path:
                host, port = self.path.split(':')
                port = int(port)
            else:
                host = self.path
                port = 443
        except ValueError:
            self.send_error_response(400, "Formato inválido para CONNECT")
            return

        try:
            import socket

            # Conecta ao servidor destino
            remote_socket = socket.create_connection((host, port), timeout=30)

            # Envia 200 Connection Established
            self.send_response(200, 'Connection Established')
            self.end_headers()

            # Faz tunnel bidirecional
            self.connection.setblocking(False)
            remote_socket.setblocking(False)

            import select

            sockets = [self.connection, remote_socket]
            timeout = 60

            while True:
                readable, _, exceptional = select.select(sockets, [], sockets, timeout)

                if exceptional:
                    break

                if not readable:
                    break  # timeout

                for sock in readable:
                    try:
                        data = sock.recv(8192)
                        if not data:
                            raise ConnectionError("Connection closed")

                        if sock is self.connection:
                            remote_socket.sendall(data)
                        else:
                            self.connection.sendall(data)

                        with self.stats_lock:
                            self.stats['bytes_transferred'] += len(data)

                    except (BlockingIOError, ssl.SSLWantReadError):
                        continue
                    except Exception:
                        break

            remote_socket.close()
            logger.info(f"TUNNEL: {host}:{port} closed")

        except Exception as e:
            self.send_error_response(502, f"Erro ao criar tunnel: {e}")
            logger.error(f"CONNECT error: {host}:{port} -> {e}")


class ThreadedHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer):
    """HTTP Server com suporte a threads"""
    daemon_threads = True
    allow_reuse_address = True


def main():
    parser = argparse.ArgumentParser(
        description='Proxy HTTP com whitelist de IPs',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Exemplos:
    python3 ip-whitelist-proxy.py
    python3 ip-whitelist-proxy.py --port 3128
    python3 ip-whitelist-proxy.py --whitelist /path/to/whitelist.txt
    python3 ip-whitelist-proxy.py --add-ip 203.0.113.50 --add-ip 198.51.100.0/24

Formato do arquivo whitelist (um por linha):
    192.168.1.0/24
    10.0.0.0/8
    100.75.88.8
    # Comentários são ignorados

Uso do proxy:
    curl -x http://localhost:8888 http://example.com
    curl --proxy http://localhost:8888 https://api.example.com/data

Endpoints de gerenciamento:
    http://localhost:8888/_proxy/status     - Status do proxy
    http://localhost:8888/_proxy/whitelist  - Lista whitelist
    http://localhost:8888/_proxy/whitelist/add    - Adiciona IP (POST)
    http://localhost:8888/_proxy/whitelist/remove - Remove IP (POST)
    http://localhost:8888/_proxy/history    - Histórico de requisições
    http://localhost:8888/_proxy/stats      - Estatísticas agregadas
        """
    )

    parser.add_argument('-p', '--port', type=int, default=DEFAULT_PORT,
                       help=f'Porta do proxy (default: {DEFAULT_PORT})')
    parser.add_argument('-w', '--whitelist', type=str,
                       help='Arquivo com whitelist de IPs')
    parser.add_argument('--add-ip', action='append', dest='extra_ips',
                       help='IPs/subnets adicionais (pode repetir)')
    parser.add_argument('--no-defaults', action='store_true',
                       help='Não carregar whitelist padrão')
    parser.add_argument('-v', '--verbose', action='store_true',
                       help='Modo verbose (debug)')

    args = parser.parse_args()

    if args.verbose:
        logger.setLevel(logging.DEBUG)

    # Inicializa whitelist
    whitelist = IPWhitelist()

    # Carrega defaults
    if not args.no_defaults:
        whitelist.load_from_list(DEFAULT_WHITELIST)
        logger.info(f"Whitelist padrão carregada: {len(DEFAULT_WHITELIST)} entradas")

    # Carrega de arquivo
    if args.whitelist:
        whitelist.load_from_file(args.whitelist)

    # Adiciona IPs extras da linha de comando
    if args.extra_ips:
        for ip in args.extra_ips:
            whitelist.add(ip)

    # Configura handler
    ProxyHandler.whitelist = whitelist
    ProxyHandler.stats['start_time'] = datetime.now()

    # Inicia servidor
    server_address = ('0.0.0.0', args.port)
    httpd = ThreadedHTTPServer(server_address, ProxyHandler)

    print(f"""
╔══════════════════════════════════════════════════════════════╗
║              IP WHITELIST PROXY SERVER                       ║
╠══════════════════════════════════════════════════════════════╣
║  Porta:     {args.port:<48}║
║  Whitelist: {len(whitelist)} IPs/subnets{' ' * (38 - len(str(len(whitelist))))}║
║  Bind:      0.0.0.0 (todas interfaces)                       ║
╠══════════════════════════════════════════════════════════════╣
║  Status:    http://localhost:{args.port}/_proxy/status{' ' * (24 - len(str(args.port)))}║
║  Whitelist: http://localhost:{args.port}/_proxy/whitelist{' ' * (21 - len(str(args.port)))}║
╚══════════════════════════════════════════════════════════════╝

Uso: curl -x http://localhost:{args.port} http://example.com
     curl --proxy http://localhost:{args.port} https://api.example.com

Ctrl+C para parar
""")

    # Mostra whitelist atual
    wl_data = whitelist.get_all()
    if wl_data['single_ips']:
        print(f"IPs únicos: {', '.join(wl_data['single_ips'])}")
    if wl_data['networks']:
        print(f"Subnets:    {', '.join(wl_data['networks'])}")
    print()

    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        print("\n\nEncerrando proxy...")
        httpd.shutdown()

        # Mostra estatísticas finais
        stats = ProxyHandler.stats
        print(f"""
Estatísticas finais:
  Requisições totais:  {stats['requests_total']}
  Permitidas:          {stats['requests_allowed']}
  Bloqueadas:          {stats['requests_blocked']}
  Bytes transferidos:  {stats['bytes_transferred']:,}
""")


if __name__ == '__main__':
    main()
