# IP Management Implementation Plan

## Overview

This document provides detailed implementation guidance for the IP Management feature, including specific code changes, file locations, and integration points.

## 1. IP Manager Module (`core/ip_manager.py`)

### Class Structure

```python
import json
import os
import time
import threading
from typing import Dict, Any, Optional
from datetime import datetime
import ipaddress

from core.config import get_config
from core.logger import get_logger

class IPManager:
    """Manages IP address access control for RateLoader HTTP server."""

    def __init__(self, ip_file: str = "ip_access.json"):
        """Initialize IP manager with storage file."""
        self.config = get_config()
        self.logger = get_logger()
        self.ip_file = ip_file
        self.data = {}
        self.lock = threading.RLock()
        self._load_data()

    def _load_data(self):
        """Load IP data from file or create default structure."""

    def _save_data(self):
        """Save IP data to file with backup."""

    def is_ip_allowed(self, ip_address: str) -> bool:
        """Check if IP address is allowed to access the server."""

    def record_request(self, ip_address: str, request_type: str):
        """Record a request from an IP address."""

    def set_ip_status(self, ip_address: str, status: str):
        """Set the status of an IP address (allowed/restricted)."""

    def set_global_access(self, status: str):
        """Set global access mode (allowed/restricted)."""

    def get_all_ips(self) -> Dict[str, Dict[str, Any]]:
        """Get all tracked IP addresses with their metadata."""

    def remove_ip(self, ip_address: str):
        """Remove an IP address from tracking."""

    def _validate_ip(self, ip_address: str) -> bool:
        """Validate IP address format."""

    def _enforce_ip_limit(self):
        """Enforce maximum IP limit with LRU eviction."""
```

### Key Implementation Details

- Use threading.RLock for thread safety
- Implement file backup before writes
- Use ipaddress module for validation
- Implement LRU eviction for IP limit
- Log all operations with appropriate levels

## 2. HTTP Server Integration (`core/http_server.py`)

### Changes to RateLoaderRequestHandler

```python
class RateLoaderRequestHandler(BaseHTTPRequestHandler):
    def __init__(self, *args, oracle_connector=None, executor=None, ip_manager=None, **kwargs):
        # Add ip_manager parameter
        self.ip_manager = ip_manager
        # ... existing initialization

    def do_GET(self):
        # Add IP check at the beginning
        client_ip = self.client_address[0]
        if not self.ip_manager.is_ip_allowed(client_ip):
            self.logger.warning(f"Access denied for IP {client_ip}")
            self._send_403()
            return

        # Record the request
        self.ip_manager.record_request(client_ip, "GET")

        # ... existing GET logic

    def do_POST(self):
        # Add IP check at the beginning
        client_ip = self.client_address[0]
        if not self.ip_manager.is_ip_allowed(client_ip):
            self.logger.warning(f"Access denied for IP {client_ip}")
            self._send_403()
            return

        # Record the request
        self.ip_manager.record_request(client_ip, "POST")

        # ... existing POST logic

    def _send_403(self):
        """Send 403 Forbidden response."""
        self.send_response(403)
        self.send_header('Content-Type', 'application/json')
        self.end_headers()
        response = {"error": "Access denied", "message": "Your IP address is not allowed to access this service"}
        self.wfile.write(json.dumps(response).encode('utf-8'))
```

### Changes to RateLoaderHTTPServer

```python
class RateLoaderHTTPServer:
    def __init__(self, port: Optional[int] = None, max_workers: int = 5):
        # ... existing initialization
        self.ip_manager = None  # Will be set later

    def set_ip_manager(self, ip_manager):
        """Set the IP manager instance."""
        self.ip_manager = ip_manager

    def start(self) -> bool:
        # ... existing code
        def handler_factory(*args, **kwargs):
            return RateLoaderRequestHandler(
                *args,
                oracle_connector=self.oracle_connector,
                executor=self.executor,
                ip_manager=self.ip_manager,  # Pass IP manager
                **kwargs
            )
        # ... rest of existing code
```

## 3. Configuration Integration (`core/config.py`)

### Add IP Management Defaults

```python
def _create_default_config(self) -> None:
    # ... existing code
    self._defaults = {
        "settings": {
            # ... existing settings
            # IP Management
            "ip_file": "ip_access.json",
            "auto_add_new_ips": "1",
            "log_denied_requests": "1",
            "max_tracked_ips": "1000",
        }
    }
```

## 4. GUI Implementation (`gui/app.py`)

### Add IP Management Tab Creation

```python
def _setup_main_layout(self):
    # ... existing code
    self._create_rate_loader_tab()
    self._create_export_mailer_tab()
    self._create_price_export_tab()
    self._create_ip_management_tab()  # Add this line
    self._create_settings_tab()
    self._create_logs_tab()

def _create_ip_management_tab(self):
    """Create the IP Management tab."""
    self.ip_management_frame = ttk.Frame(self.notebook)
    self.notebook.add(self.ip_management_frame, text="IP Management")

    # Global access control
    global_frame = ttk.LabelFrame(self.ip_management_frame, text="Global Access Control", padding=10)
    global_frame.pack(fill=tk.X, padx=10, pady=10)

    self.global_access_var = tk.StringVar(value="allowed")
    ttk.Radiobutton(global_frame, text="Allowed (Blacklist Mode)",
                   variable=self.global_access_var, value="allowed",
                   command=self._on_global_access_changed).pack(side=tk.LEFT, padx=5)
    ttk.Radiobutton(global_frame, text="Restricted (Whitelist Mode)",
                   variable=self.global_access_var, value="restricted",
                   command=self._on_global_access_changed).pack(side=tk.LEFT, padx=5)

    # IP list
    list_frame = ttk.LabelFrame(self.ip_management_frame, text="IP Addresses", padding=10)
    list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

    # Create treeview for IP list
    columns = ("IP Address", "Status", "Last Seen", "Requests", "Last Request")
    self.ip_tree = ttk.Treeview(list_frame, columns=columns, show="headings", height=15)

    for col in columns:
        self.ip_tree.heading(col, text=col)
        self.ip_tree.column(col, width=120)

    # Scrollbar for treeview
    scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.ip_tree.yview)
    self.ip_tree.configure(yscrollcommand=scrollbar.set)

    self.ip_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
    scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

    # Action buttons
    button_frame = ttk.Frame(self.ip_management_frame)
    button_frame.pack(fill=tk.X, padx=10, pady=10)

    ttk.Button(button_frame, text="Toggle Selected", command=self._toggle_selected_ip).pack(side=tk.LEFT, padx=5)
    ttk.Button(button_frame, text="Remove Selected", command=self._remove_selected_ip).pack(side=tk.LEFT, padx=5)
    ttk.Button(button_frame, text="Refresh", command=self._refresh_ip_list).pack(side=tk.LEFT, padx=5)
    ttk.Button(button_frame, text="Export List", command=self._export_ip_list).pack(side=tk.LEFT, padx=5)

    # Status bar
    self.ip_status_label = ttk.Label(self.ip_management_frame, text="Total IPs: 0 | Allowed: 0 | Restricted: 0")
    self.ip_status_label.pack(fill=tk.X, padx=10, pady=5)
```

### Add IP Management Event Handlers

```python
def _on_global_access_changed(self):
    """Handle global access mode change."""
    try:
        new_mode = self.global_access_var.get()
        if hasattr(self, 'ip_manager') and self.ip_manager:
            self.ip_manager.set_global_access(new_mode)
            self._log_to_ip_management(f"Global access mode changed to: {new_mode}")
    except Exception as e:
        self._show_error(f"Error changing global access mode: {str(e)}")

def _toggle_selected_ip(self):
    """Toggle status of selected IP address."""
    try:
        selection = self.ip_tree.selection()
        if not selection:
            messagebox.showwarning("Selection", "Please select an IP address to toggle")
            return

        item = self.ip_tree.item(selection[0])
        ip_address = item['values'][0]
        current_status = item['values'][1]

        new_status = "restricted" if current_status == "allowed" else "allowed"

        if hasattr(self, 'ip_manager') and self.ip_manager:
            self.ip_manager.set_ip_status(ip_address, new_status)
            self._refresh_ip_list()
            self._log_to_ip_management(f"IP {ip_address} status changed to: {new_status}")
    except Exception as e:
        self._show_error(f"Error toggling IP status: {str(e)}")

def _remove_selected_ip(self):
    """Remove selected IP address from tracking."""
    try:
        selection = self.ip_tree.selection()
        if not selection:
            messagebox.showwarning("Selection", "Please select an IP address to remove")
            return

        item = self.ip_tree.item(selection[0])
        ip_address = item['values'][0]

        if messagebox.askyesno("Confirm Removal", f"Remove IP {ip_address} from tracking?"):
            if hasattr(self, 'ip_manager') and self.ip_manager:
                self.ip_manager.remove_ip(ip_address)
                self._refresh_ip_list()
                self._log_to_ip_management(f"IP {ip_address} removed from tracking")
    except Exception as e:
        self._show_error(f"Error removing IP: {str(e)}")

def _refresh_ip_list(self):
    """Refresh the IP address list."""
    try:
        # Clear existing items
        for item in self.ip_tree.get_children():
            self.ip_tree.delete(item)

        if hasattr(self, 'ip_manager') and self.ip_manager:
            ips = self.ip_manager.get_all_ips()
            global_access = self.ip_manager.data.get('global_access', 'allowed')

            allowed_count = 0
            restricted_count = 0

            for ip_address, data in ips.get('ips', {}).items():
                status = data.get('status', 'allowed')
                last_seen = data.get('last_seen', 'Never')
                request_count = data.get('request_count', 0)
                last_request = data.get('last_request_type', 'N/A')

                self.ip_tree.insert("", tk.END, values=(
                    ip_address, status, last_seen, request_count, last_request
                ))

                if status == 'allowed':
                    allowed_count += 1
                else:
                    restricted_count += 1

            total_count = allowed_count + restricted_count
            self.ip_status_label.config(
                text=f"Total IPs: {total_count} | Allowed: {allowed_count} | Restricted: {restricted_count}"
            )
    except Exception as e:
        self._show_error(f"Error refreshing IP list: {str(e)}")

def _export_ip_list(self):
    """Export IP list to CSV file."""
    try:
        if not hasattr(self, 'ip_manager') or not self.ip_manager:
            messagebox.showwarning("Export", "IP Manager not available")
            return

        file_path = filedialog.asksaveasfilename(
            defaultextension=".csv",
            filetypes=[("CSV files", "*.csv"), ("All files", "*.*")]
        )

        if file_path:
            ips = self.ip_manager.get_all_ips()

            with open(file_path, 'w', newline='', encoding='utf-8') as csvfile:
                writer = csv.writer(csvfile)
                writer.writerow(['IP Address', 'Status', 'First Seen', 'Last Seen', 'Request Count', 'Last Request Type'])

                for ip_address, data in ips.get('ips', {}).items():
                    writer.writerow([
                        ip_address,
                        data.get('status', 'allowed'),
                        data.get('first_seen', ''),
                        data.get('last_seen', ''),
                        data.get('request_count', 0),
                        data.get('last_request_type', '')
                    ])

            messagebox.showinfo("Export", f"IP list exported to {file_path}")
            self._log_to_ip_management(f"IP list exported to {file_path}")
    except Exception as e:
        self._show_error(f"Error exporting IP list: {str(e)}")

def _log_to_ip_management(self, message):
    """Log message to IP Management status (if implemented)."""
    # This could be added to a status text widget in the future
    self.logger.info(f"IP Management: {message}")
```

### Initialize IP Manager in GUI

```python
def __init__(self):
    # ... existing initialization
    self.ip_manager = None  # Will be initialized after HTTP server

    # ... rest of existing initialization

def _start_background_threads(self):
    """Start background threads for task queue and scheduler."""
    # ... existing code

    # Initialize IP manager after HTTP server is available
    from core.ip_manager import IPManager
    self.ip_manager = IPManager()

    # Set IP manager in HTTP server
    if self.http_server:
        self.http_server.set_ip_manager(self.ip_manager)
```

## 5. Integration Points

### Application Startup Sequence

1. Initialize core components (config, logger, etc.)
2. Create HTTP server instance
3. Create IP manager instance
4. Set IP manager in HTTP server
5. Start HTTP server
6. Initialize GUI with IP management tab

### Request Processing Flow

1. HTTP request received
2. Extract client IP address
3. Check IP access permissions
4. If denied, return 403 and log
5. If allowed, record request and process normally

### GUI Update Flow

1. User opens IP Management tab
2. GUI requests IP list from IP manager
3. IP manager loads data from JSON file
4. GUI displays IP list with current status
5. User actions update IP manager
6. IP manager saves changes to JSON file

## 6. Testing Strategy

### Unit Tests

- Test IP address validation
- Test access control logic
- Test file read/write operations
- Test LRU eviction
- Test GUI component interactions

### Integration Tests

- Test HTTP server integration
- Test end-to-end request flow
- Test configuration persistence
- Test GUI refresh functionality

### Security Tests

- Test with malformed IP addresses
- Test with large numbers of requests
- Test file permission scenarios
- Test concurrent access scenarios

## 7. Error Handling

### File Operations

- Handle missing or corrupted JSON file
- Create default file if none exists
- Backup file before writes
- Handle file permission errors

### GUI Errors

- Handle IP manager not available
- Handle invalid IP selections
- Show user-friendly error messages
- Provide recovery options

### HTTP Server Errors

- Handle IP manager failures gracefully
- Log access denied attempts
- Continue operation if IP tracking fails
- Provide fallback behavior
