"""
HTTP server implementation for the client to serve content.
"""
import os
import json
import threading
import socket
from http.server import HTTPServer, BaseHTTPRequestHandler
from http import HTTPStatus
import ssl
import time
from urllib.parse import urlparse, parse_qs, unquote
import base64
import mimetypes
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
from ...shared.utils import setup_logging, ensure_directory_exists, get_data_dir
from ...shared.security import SecurityManager
from ...shared.domain import Domain
from ...shared.constants import DEFAULT_PORT

# Setup logger
logger = setup_logging('http_server')

class DomainHandler(BaseHTTPRequestHandler):
    """HTTP request handler for domain content."""
    
    def __init__(self, *args, **kwargs):
        # BaseHTTPRequestHandler calls do_METHOD method for each
        # HTTP request it receives.
        BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
    
    def log_message(self, format, *args):
        """Override to use our logger."""
        logger.info(f"{self.address_string()} - {format % args}")
    
    def send_cors_headers(self):
        """Send CORS headers."""
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
        self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
    
    def do_OPTIONS(self):
        """Handle OPTIONS requests for CORS."""
        self.send_response(HTTPStatus.NO_CONTENT)
        self.send_cors_headers()
        self.end_headers()
    
    def do_GET(self):
        """Handle GET requests."""
        try:
            parsed_path = urlparse(self.path)
            path = parsed_path.path
            
            # Check if path is for domain info
            if path == '/' or path == '/info':
                self.send_domain_info()
                return
            
            # Check if path is for a file
            if path.startswith('/file/'):
                file_path = unquote(path[6:])  # Remove '/file/' prefix
                self.send_file(file_path)
                return
            
            # Check if path is for favicon
            if path == '/favicon.ico' and hasattr(self.server, 'domain'):
                self.send_favicon()
                return
            
            # Check if path is for banner
            if path == '/banner' and hasattr(self.server, 'domain'):
                self.send_banner()
                return
            
            # Handle unknown paths
            self.send_error(404, 'Not Found')
            
        except Exception as e:
            logger.error(f"Error handling request: {str(e)}")
            self.send_error(500, f'Internal Server Error: {str(e)}')
    
    def do_POST(self):
        """Handle POST requests."""
        try:
            parsed_path = urlparse(self.path)
            path = parsed_path.path
            
            # Get content length
            content_length = int(self.headers.get('Content-Length', 0))
            body = self.rfile.read(content_length).decode('utf-8')
            
            # Check if path is for PFS initialization
            if path == '/pfs/init':
                self.handle_pfs_init(body)
                return
            
            # Check if path is for PFS key exchange
            if path == '/pfs/exchange':
                self.handle_pfs_exchange(body)
                return
            
            # Check if path is for P2P messaging
            if path == '/p2p/message':
                self.handle_p2p_message(body)
                return
            
            # Handle unknown paths
            self.send_error(404, 'Not Found')
            
        except Exception as e:
            logger.error(f"Error handling request: {str(e)}")
            self.send_error(500, f'Internal Server Error: {str(e)}')
    
    def send_domain_info(self):
        """Send domain information."""
        if not hasattr(self.server, 'domain'):
            self.send_error(404, 'Domain not configured')
            return
        
        domain_info = self.server.domain.to_dict()
        
        # Add file list if available
        if hasattr(self.server, 'content_dir') and os.path.exists(self.server.content_dir):
            file_list = []
            for root, _, files in os.walk(self.server.content_dir):
                for file in files:
                    if file.endswith('.enc'):  # Only include encrypted files
                        file_path = os.path.join(root, file)
                        rel_path = os.path.relpath(file_path, self.server.content_dir)
                        file_size = os.path.getsize(file_path)
                        
                        # Get file type from original name (remove .enc)
                        original_name = file[:-4] if file.endswith('.enc') else file
                        file_type = mimetypes.guess_type(original_name)[0] or 'application/octet-stream'
                        
                        file_list.append({
                            'name': rel_path,
                            'size': file_size,
                            'type': file_type
                        })
            
            domain_info['files'] = file_list
            domain_info['file_count'] = len(file_list)
        
        self.send_response(HTTPStatus.OK)
        self.send_header('Content-Type', 'application/json')
        self.send_cors_headers()
        self.end_headers()
        
        self.wfile.write(json.dumps(domain_info).encode())
    
    def send_file(self, file_path):
        
        if not hasattr(self.server, 'content_dir'):
            self.send_error(500, 'Content directory not configured')
            return
        
        # Ensure file_path doesn't contain directory traversal
        norm_path = os.path.normpath(file_path)
        if os.path.isabs(norm_path) or norm_path.startswith('..'):
            self.send_error(403, 'Forbidden')
            return
        
        # Check if this is a direct request for an encrypted file
        is_enc = norm_path.endswith('.enc')
        if is_enc:
            norm_path = norm_path  # Keep as is
        else:
            # Try with .enc extension
            norm_path_enc = norm_path + '.enc'
        
        # Build full paths
        full_path = os.path.join(self.server.content_dir, norm_path)
        if not is_enc:
            full_path_enc = os.path.join(self.server.content_dir, norm_path_enc)
        
        # Check which file exists
        if os.path.exists(full_path):
            # Regular file exists
            use_path = full_path
            key_path = full_path + '.key'
            has_key = os.path.exists(key_path)
        elif not is_enc and os.path.exists(full_path_enc):
            # Encrypted file exists
            use_path = full_path_enc
            key_path = full_path + '.key'
            has_key = os.path.exists(key_path)
        else:
            self.send_error(404, 'File not found')
            return
        
        # Read and send the file
        try:
            with open(use_path, 'rb') as f:
                file_data = f.read()
            
            # Read encryption key if exists
            encryption_key = None
            if has_key:
                try:
                    with open(key_path, 'rb') as f:
                        encryption_key = f.read()
                except Exception as e:
                    logger.error(f"Error reading key file: {str(e)}")
            
            # Get the filename for Content-Disposition
            file_name = os.path.basename(file_path)
            
            # Send response headers
            self.send_response(HTTPStatus.OK)
            self.send_header('Content-Type', 'application/octet-stream')
            self.send_header('Content-Disposition', f'attachment; filename="{file_name}"')
            self.send_header('Content-Length', str(len(file_data)))
            
            # Send encryption key if available
            if encryption_key:
                encoded_key = base64.b64encode(encryption_key).decode('ascii')
                self.send_header('X-Encryption-Key', encoded_key)
            
            self.send_cors_headers()
            self.end_headers()
            
            # Send the file data
            self.wfile.write(file_data)
            
        except Exception as e:
            logger.error(f"Error sending file: {str(e)}")
            self.send_error(500, f'Internal Server Error: {str(e)}')
    def send_favicon(self):
        """Send the domain favicon."""
        if not hasattr(self.server, 'domain') or not self.server.domain.favicon:
            self.send_error(404, 'Favicon not found')
            return
        
        try:
            # Decode base64 favicon data
            favicon_data = base64.b64decode(self.server.domain.favicon)
            
            self.send_response(HTTPStatus.OK)
            self.send_header('Content-Type', 'image/x-icon')
            self.send_header('Content-Length', str(len(favicon_data)))
            self.send_cors_headers()
            self.end_headers()
            
            self.wfile.write(favicon_data)
            
        except Exception as e:
            logger.error(f"Error sending favicon: {str(e)}")
            self.send_error(500, f'Internal Server Error: {str(e)}')
    
    def send_banner(self):
        """Send the domain banner."""
        if not hasattr(self.server, 'domain') or not self.server.domain.banner:
            self.send_error(404, 'Banner not found')
            return
        
        try:
            # Decode base64 banner data
            banner_data = base64.b64decode(self.server.domain.banner)
            
            self.send_response(HTTPStatus.OK)
            self.send_header('Content-Type', 'image/jpeg')  # Assume JPEG for banners
            self.send_header('Content-Length', str(len(banner_data)))
            self.send_cors_headers()
            self.end_headers()
            
            self.wfile.write(banner_data)
            
        except Exception as e:
            logger.error(f"Error sending banner: {str(e)}")
            self.send_error(500, f'Internal Server Error: {str(e)}')
    
    def handle_pfs_init(self, body):
        """Handle PFS initialization request."""
        if not hasattr(self.server, 'security_manager'):
            self.send_error(500, 'Security manager not configured')
            return
        
        try:
            # Parse request
            data = json.loads(body)
            client_id = data.get('client_id')
            
            if not client_id:
                self.send_error(400, 'Missing client ID')
                return
            
            # Initialize PFS session
            pfs_session = self.server.security_manager.pfs_init()
            
            # Store session for this client
            if not hasattr(self.server, 'pfs_sessions'):
                self.server.pfs_sessions = {}
            
            self.server.pfs_sessions[client_id] = pfs_session
            
            # Send public key to client
            public_key_pem = pfs_session['public_key'].public_bytes(
                encoding=serialization.Encoding.PEM,
                format=serialization.PublicFormat.SubjectPublicKeyInfo
            )
            
            response = {
                'session_id': pfs_session['session_id'],
                'public_key': public_key_pem.decode('utf-8')
            }
            
            self.send_response(HTTPStatus.OK)
            self.send_header('Content-Type', 'application/json')
            self.send_cors_headers()
            self.end_headers()
            
            self.wfile.write(json.dumps(response).encode())
            
        except Exception as e:
            logger.error(f"Error handling PFS init: {str(e)}")
            self.send_error(500, f'Internal Server Error: {str(e)}')
    
    def handle_pfs_exchange(self, body):
        """Handle PFS key exchange request."""
        if not hasattr(self.server, 'security_manager') or not hasattr(self.server, 'pfs_sessions'):
            self.send_error(500, 'Security manager or PFS sessions not configured')
            return
        
        try:
            # Parse request
            data = json.loads(body)
            client_id = data.get('client_id')
            session_id = data.get('session_id')
            encrypted_secret = data.get('encrypted_secret')
            
            if not client_id or not session_id or not encrypted_secret:
                self.send_error(400, 'Missing required fields')
                return
            
            # Get session
            session = self.server.pfs_sessions.get(client_id)
            if not session or session['session_id'] != session_id:
                self.send_error(403, 'Invalid session')
                return
            
            # Decode encrypted secret
            encrypted_secret_bytes = base64.b64decode(encrypted_secret)
            
            # Process exchange
            success = self.server.security_manager.pfs_receive(session, encrypted_secret_bytes)
            
            if not success:
                self.send_error(500, 'Failed to process key exchange')
                return
            
            response = {
                'success': True,
                'message': 'PFS session established'
            }
            
            self.send_response(HTTPStatus.OK)
            self.send_header('Content-Type', 'application/json')
            self.send_cors_headers()
            self.end_headers()
            
            self.wfile.write(json.dumps(response).encode())
            
        except Exception as e:
            logger.error(f"Error handling PFS exchange: {str(e)}")
            self.send_error(500, f'Internal Server Error: {str(e)}')
    
    def handle_p2p_message(self, body):
        """Handle P2P message request."""
        if not hasattr(self.server, 'security_manager') or not hasattr(self.server, 'pfs_sessions'):
            self.send_error(500, 'Security manager or PFS sessions not configured')
            return
        
        try:
            # Parse request
            data = json.loads(body)
            client_id = data.get('client_id')
            session_id = data.get('session_id')
            encrypted_message = data.get('encrypted_message')
            
            if not client_id or not session_id or not encrypted_message:
                self.send_error(400, 'Missing required fields')
                return
            
            # Get session
            session = self.server.pfs_sessions.get(client_id)
            if not session or session['session_id'] != session_id or not session.get('established'):
                self.send_error(403, 'Invalid or not established session')
                return
            
            # Decode encrypted message
            encrypted_message_bytes = base64.b64decode(encrypted_message)
            
            # Decrypt message
            try:
                decrypted_message = self.server.security_manager.pfs_decrypt(
                    session, encrypted_message_bytes
                )
                
                # Process the message
                message_text = decrypted_message.decode('utf-8')
                
                # In a real implementation, you would handle the message here
                # For example, display it to the user or process a command
                
                # Add to message history if available
                if hasattr(self.server, 'message_history'):
                    self.server.message_history.append({
                        'from': client_id,
                        'timestamp': time.time(),
                        'message': message_text
                    })
                
                # Send response
                response = {
                    'success': True,
                    'message': 'Message received'
                }
                
                self.send_response(HTTPStatus.OK)
                self.send_header('Content-Type', 'application/json')
                self.send_cors_headers()
                self.end_headers()
                
                self.wfile.write(json.dumps(response).encode())
                
            except Exception as e:
                logger.error(f"Error decrypting message: {str(e)}")
                self.send_error(500, f'Failed to decrypt message: {str(e)}')
                
        except Exception as e:
            logger.error(f"Error handling P2P message: {str(e)}")
            self.send_error(500, f'Internal Server Error: {str(e)}')


class DomainServer:
    """HTTP server for serving domain content."""
    
    def __init__(self, domain, security_manager, content_dir, port=DEFAULT_PORT):
        """Initialize the domain server."""
        self.domain = domain
        self.security_manager = security_manager
        self.content_dir = ensure_directory_exists(content_dir)
        self.port = port
        
        self.server = None
        self.server_thread = None
        self.running = False
        
        # Message history for P2P communication
        self.message_history = []
        
        # PFS sessions
        self.pfs_sessions = {}
    
    def start(self):
        """Start the HTTP server."""
        if self.running:
            logger.warning("Server already running")
            return
        
        try:
            # Create server
            self.server = HTTPServer(('0.0.0.0', self.port), DomainHandler)
            
            # Attach references to our attributes
            self.server.domain = self.domain
            self.server.security_manager = self.security_manager
            self.server.content_dir = self.content_dir
            self.server.message_history = self.message_history
            self.server.pfs_sessions = self.pfs_sessions
            
            # Start server in a separate thread
            self.server_thread = threading.Thread(target=self.server.serve_forever)
            self.server_thread.daemon = True
            self.server_thread.start()
            
            self.running = True
            logger.info(f"Domain server started on port {self.port}")
            
            return True
            
        except Exception as e:
            logger.error(f"Failed to start domain server: {str(e)}")
            return False
    
    def stop(self):
        """Stop the HTTP server."""
        if not self.running:
            logger.warning("Server not running")
            return
        
        try:
            self.server.shutdown()
            self.server.server_close()
            self.server_thread.join()
            
            self.running = False
            logger.info("Domain server stopped")
            
            return True
            
        except Exception as e:
            logger.error(f"Failed to stop domain server: {str(e)}")
            return False
    
    def is_running(self):
        """Check if the server is running."""
        return self.running