#!/usr/bin/env python3
"""
Framework-Native Dashboard API Server
Provides live data and contract deployment capabilities
"""

import os
import json
import time
import hashlib
import subprocess
from datetime import datetime
from pathlib import Path
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import parse_qs, urlparse
import threading

# Configuration
DEPLOYMENT_DIR = Path(__file__).parent / "deployment"
LOGS_DIR = DEPLOYMENT_DIR / "logs"
NODES_DIR = DEPLOYMENT_DIR / "nodes"
CONTRACTS_DIR = DEPLOYMENT_DIR / "contracts"

# Ensure directories exist
CONTRACTS_DIR.mkdir(parents=True, exist_ok=True)

# Contract storage
deployed_contracts = []
execution_logs = []

class FrameworkNativeContract:
    """Framework-native analog contract"""

    def __init__(self, name, code, metadata=None):
        self.name = name
        self.code = code
        self.metadata = metadata or {}
        self.deployed_at = datetime.now().isoformat()
        self.contract_hash = self._compute_hash()
        self.execution_count = 0
        self.last_execution = None

    def _compute_hash(self):
        """Compute contract hash"""
        data = f"{self.name}:{self.code}:{self.deployed_at}"
        return hashlib.sha256(data.encode()).hexdigest()[:16]

    def to_dict(self):
        """Convert to dictionary"""
        return {
            'name': self.name,
            'code': self.code,
            'hash': self.contract_hash,
            'deployed_at': self.deployed_at,
            'execution_count': self.execution_count,
            'last_execution': self.last_execution,
            'metadata': self.metadata
        }

    def save(self):
        """Save contract to disk"""
        contract_file = CONTRACTS_DIR / f"{self.contract_hash}.json"
        with open(contract_file, 'w') as f:
            json.dump(self.to_dict(), f, indent=2)

    def execute(self, context=None):
        """Execute contract code"""
        context = context or {}
        exec_globals = {
            'context': context,
            'result': {},
            'datetime': datetime,
            'time': time
        }

        try:
            exec(self.code, exec_globals)
            result = exec_globals.get('result', {})

            self.execution_count += 1
            self.last_execution = datetime.now().isoformat()

            log_entry = {
                'contract': self.name,
                'hash': self.contract_hash,
                'timestamp': self.last_execution,
                'result': result,
                'success': True
            }
            execution_logs.append(log_entry)

            return log_entry

        except Exception as e:
            error_log = {
                'contract': self.name,
                'hash': self.contract_hash,
                'timestamp': datetime.now().isoformat(),
                'error': str(e),
                'success': False
            }
            execution_logs.append(error_log)
            return error_log


class DashboardAPIHandler(BaseHTTPRequestHandler):
    """HTTP request handler for dashboard API"""

    def _set_headers(self, content_type='application/json', status=200):
        """Set response headers"""
        self.send_response(status)
        self.send_header('Content-type', content_type)
        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')
        self.send_header('Cache-Control', 'no-store, no-cache, must-revalidate')
        self.end_headers()

    def do_OPTIONS(self):
        """Handle OPTIONS request for CORS"""
        self._set_headers()

    def do_GET(self):
        """Handle GET requests"""
        parsed = urlparse(self.path)
        path = parsed.path

        # Serve static files
        if path == '/' or path == '/dashboard.html':
            self._serve_file('dashboard.html', 'text/html')
            return
        elif path.endswith('.html'):
            self._serve_file(path[1:], 'text/html')
            return
        elif path.endswith('.css'):
            self._serve_file(path[1:], 'text/css')
            return
        elif path.endswith('.js'):
            self._serve_file(path[1:], 'application/javascript')
            return

        try:
            if path == '/api/status':
                self._handle_status()
            elif path == '/api/nodes':
                self._handle_nodes()
            elif path == '/api/contracts':
                self._handle_contracts()
            elif path == '/api/logs':
                self._handle_logs()
            elif path.startswith('/api/node/'):
                node_num = path.split('/')[-1]
                self._handle_node_data(node_num)
            else:
                self._send_error(404, 'Endpoint not found')
        except Exception as e:
            self._send_error(500, str(e))

    def _serve_file(self, filename, content_type):
        """Serve a static file"""
        try:
            filepath = Path(__file__).parent / filename
            if not filepath.exists():
                self._send_error(404, f'File not found: {filename}')
                return

            with open(filepath, 'r', encoding='utf-8') as f:
                content = f.read()

            self._set_headers(content_type, 200)
            self.wfile.write(content.encode('utf-8'))
        except Exception as e:
            self._send_error(500, f'Error serving file: {str(e)}')

    def do_POST(self):
        """Handle POST requests"""
        parsed = urlparse(self.path)
        path = parsed.path

        try:
            content_length = int(self.headers['Content-Length'])
            post_data = self.rfile.read(content_length).decode('utf-8')
            data = json.loads(post_data)

            if path == '/api/deploy':
                self._handle_deploy(data)
            elif path == '/api/execute':
                self._handle_execute(data)
            elif path == '/api/redeploy':
                self._handle_redeploy(data)
            else:
                self._send_error(404, 'Endpoint not found')
        except Exception as e:
            self._send_error(500, str(e))

    def _handle_status(self):
        """Get overall cluster status"""
        nodes = self._get_all_nodes()
        active_nodes = [n for n in nodes if n['active']]

        status = {
            'timestamp': datetime.now().isoformat(),
            'nodes_total': len(nodes),
            'nodes_active': len(active_nodes),
            'avg_compression': sum(n['compression'] for n in active_nodes) / len(active_nodes) if active_nodes else 0,
            'dna_consensus': active_nodes[0]['dna'] if active_nodes else 'N/A',
            'contracts_deployed': len(deployed_contracts),
            'total_executions': sum(c.execution_count for c in deployed_contracts)
        }

        self._send_json(status)

    def _handle_nodes(self):
        """Get all node data"""
        nodes = self._get_all_nodes()
        self._send_json({'nodes': nodes})

    def _handle_node_data(self, node_num):
        """Get specific node data"""
        log_file = LOGS_DIR / f"node{node_num}.log"

        if not log_file.exists():
            self._send_error(404, f'Node {node_num} log not found')
            return

        # Try UTF-16 first (WSL output), fallback to UTF-8
        content = None
        for encoding in ['utf-16', 'utf-8', 'latin-1']:
            try:
                with open(log_file, 'r', encoding=encoding, errors='ignore') as f:
                    content = f.read()
                if 'Compression ratio' in content or 'DNA sequence' in content:
                    break
            except:
                continue

        if not content:
            with open(log_file, 'r', encoding='utf-8', errors='replace') as f:
                content = f.read()

        node_data = self._parse_log(content, node_num)
        self._send_json(node_data)

    def _handle_contracts(self):
        """Get all deployed contracts"""
        contracts = [c.to_dict() for c in deployed_contracts]
        self._send_json({'contracts': contracts, 'count': len(contracts)})

    def _handle_logs(self):
        """Get execution logs"""
        self._send_json({'logs': execution_logs[-50:]})  # Last 50 logs

    def _handle_deploy(self, data):
        """Deploy a new contract"""
        name = data.get('name', f'Contract_{int(time.time())}')
        code = data.get('code', '')
        metadata = data.get('metadata', {})

        if not code:
            self._send_error(400, 'Contract code is required')
            return

        contract = FrameworkNativeContract(name, code, metadata)
        contract.save()
        deployed_contracts.append(contract)

        response = {
            'success': True,
            'message': f'Contract {name} deployed successfully',
            'contract': contract.to_dict()
        }

        self._send_json(response)

    def _handle_execute(self, data):
        """Execute a deployed contract"""
        contract_hash = data.get('hash')
        context = data.get('context', {})

        contract = next((c for c in deployed_contracts if c.contract_hash == contract_hash), None)

        if not contract:
            self._send_error(404, 'Contract not found')
            return

        result = contract.execute(context)
        self._send_json(result)

    def _handle_redeploy(self, data):
        """Redeploy nodes"""
        num_nodes = data.get('nodes', 3)

        try:
            # Run deployment script
            result = subprocess.run(
                ['powershell', '-ExecutionPolicy', 'Bypass', '-File', 'deploy.ps1',
                 '-Mode', 'local', '-NumNodes', str(num_nodes)],
                cwd=Path(__file__).parent,
                capture_output=True,
                text=True,
                timeout=30
            )

            response = {
                'success': result.returncode == 0,
                'message': 'Redeployment completed' if result.returncode == 0 else 'Redeployment failed',
                'output': result.stdout,
                'error': result.stderr if result.returncode != 0 else None
            }

            self._send_json(response)

        except Exception as e:
            self._send_error(500, f'Redeployment error: {str(e)}')

    def _get_all_nodes(self):
        """Get data for all nodes"""
        nodes = []
        # Auto-detect number of nodes by checking log files
        max_nodes = 10  # Check up to 10 nodes
        for i in range(1, max_nodes + 1):
            log_file = LOGS_DIR / f"node{i}.log"
            if not log_file.exists():
                continue
            if log_file.exists():
                # Try UTF-16 first (WSL output), fallback to UTF-8
                content = None
                for encoding in ['utf-16', 'utf-8', 'latin-1']:
                    try:
                        with open(log_file, 'r', encoding=encoding, errors='ignore') as f:
                            content = f.read()
                        if 'Compression ratio' in content or 'DNA sequence' in content:
                            break
                    except:
                        continue
                if content:
                    nodes.append(self._parse_log(content, str(i)))
            else:
                nodes.append({
                    'node': str(i),
                    'active': False,
                    'compression': 0,
                    'dna': 'N/A',
                    'glyph': '?',
                    'time': 0,
                    'error': 0
                })
        return nodes

    def _parse_log(self, content, node_num):
        """Parse log file content"""
        import re

        compression = 0
        # Try multiple patterns
        match = re.search(r'Compression ratio:\s*([\d.]+)', content)
        if not match:
            match = re.search(r'Compression:\s*([\d.]+)', content)
        if match:
            compression = float(match.group(1))

        dna = 'N/A'
        match = re.search(r'DNA sequence:\s*(\w+)', content)
        if not match:
            match = re.search(r'DNA:\s*(\w+)', content)
        if match:
            dna = match.group(1)

        glyph = '?'
        match = re.search(r'Holographic glyph:\s*(.)', content)
        if not match:
            match = re.search(r'Glyph:\s*([^\s|]+)', content)
        if match:
            glyph = match.group(1)

        time_ms = 0
        match = re.search(r'Encoding time:\s*([\d.]+)\s*ms', content)
        if match:
            time_ms = float(match.group(1))

        error = 0
        match = re.search(r'Max errors:\s*k=([0-9.e+-]+)', content)
        if not match:
            match = re.search(r'Error:\s*k=([0-9.e+-]+)', content)
        if match:
            error = float(match.group(1))

        return {
            'node': str(node_num),
            'active': compression > 0,
            'compression': compression,
            'dna': dna,
            'glyph': glyph,
            'time': time_ms,
            'error': error
        }

    def _send_json(self, data):
        """Send JSON response"""
        self._set_headers('application/json', 200)
        self.wfile.write(json.dumps(data).encode('utf-8'))

    def _send_error(self, status, message):
        """Send error response"""
        self._set_headers('application/json', status)
        error = {'error': message, 'status': status}
        self.wfile.write(json.dumps(error).encode('utf-8'))

    def log_message(self, format, *args):
        """Custom log format"""
        timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        print(f"[{timestamp}] {self.address_string()} - {format % args}")


def load_existing_contracts():
    """Load previously deployed contracts"""
    if not CONTRACTS_DIR.exists():
        return

    for contract_file in CONTRACTS_DIR.glob('*.json'):
        try:
            with open(contract_file, 'r') as f:
                data = json.load(f)

            contract = FrameworkNativeContract(
                data['name'],
                data['code'],
                data.get('metadata', {})
            )
            contract.deployed_at = data['deployed_at']
            contract.contract_hash = data['hash']
            contract.execution_count = data.get('execution_count', 0)
            contract.last_execution = data.get('last_execution')

            deployed_contracts.append(contract)
            print(f"✅ Loaded contract: {contract.name} ({contract.contract_hash})")
        except Exception as e:
            print(f"⚠️ Failed to load {contract_file}: {e}")


def run_server(port=8080):
    """Run the API server"""
    print("=" * 80)
    print("  FRAMEWORK-NATIVE DASHBOARD API SERVER")
    print("=" * 80)
    print(f"\n🚀 Starting server on http://localhost:{port}")
    print(f"📁 Deployment directory: {DEPLOYMENT_DIR}")
    print(f"📜 Contracts directory: {CONTRACTS_DIR}")

    # Load existing contracts
    load_existing_contracts()
    print(f"\n✅ Loaded {len(deployed_contracts)} existing contracts")

    print("\n📡 API Endpoints:")
    print("   GET  /api/status      - Cluster status")
    print("   GET  /api/nodes       - All node data")
    print("   GET  /api/node/<num>  - Specific node data")
    print("   GET  /api/contracts   - Deployed contracts")
    print("   GET  /api/logs        - Execution logs")
    print("   POST /api/deploy      - Deploy contract")
    print("   POST /api/execute     - Execute contract")
    print("   POST /api/redeploy    - Redeploy nodes")

    print(f"\n🌐 Dashboard: http://localhost:{port}/dashboard.html")
    print("=" * 80)
    print("\nPress Ctrl+C to stop\n")

    server = HTTPServer(('0.0.0.0', port), DashboardAPIHandler)

    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print("\n\n🛑 Shutting down server...")
        server.shutdown()
        print("✅ Server stopped")


if __name__ == '__main__':
    import sys
    port = int(sys.argv[1]) if len(sys.argv) > 1 else 8080
    run_server(port)
