#!/usr/bin/env python3
"""
NovaLang Runtime - Execution Engine with Real Code Execution
Handles variables, functions, classes, control flow, and database operations
"""

import os
import sys
import sqlite3
import glob
from typing import Dict, List, Any, Optional, Callable
from dataclasses import dataclass, field
from pathlib import Path

# Try to import config reader
try:
    from config_reader import NovaLangConfig
    HAS_CONFIG = True
except ImportError:
    HAS_CONFIG = False

# Try to import MySQL connector
try:
    import mysql.connector
    HAS_MYSQL = True
except ImportError:
    HAS_MYSQL = False

@dataclass
class Scope:
    """Variable scope management"""
    variables: Dict[str, Any] = field(default_factory=dict)
    parent: Optional['Scope'] = None
    
    def get(self, name: str) -> Any:
        if name in self.variables:
            return self.variables[name]
        if self.parent:
            return self.parent.get(name)
        raise NameError(f"Variable '{name}' not defined")
    
    def set(self, name: str, value: Any):
        self.variables[name] = value
    
    def exists(self, name: str) -> bool:
        return name in self.variables or (self.parent and self.parent.exists(name))

@dataclass
class NovaFunction:
    """Function object"""
    name: str
    parameters: List[str]
    body: List[str]
    scope: Scope

@dataclass
class NovaClass:
    """Class object"""
    name: str
    properties: Dict[str, Any]
    methods: Dict[str, NovaFunction]
    annotations: List[Dict[str, Any]]

class NovaLangRuntime:
    """Complete runtime for executing NovaLang code"""
    
    def __init__(self, config_path: str = "novalang.config", workspace_path: str = "."):
        self.global_scope = Scope()
        self.current_scope = self.global_scope
        self.functions: Dict[str, NovaFunction] = {}
        self.classes: Dict[str, NovaClass] = {}
        self.db_connection = None
        self.workspace_path = workspace_path
        
        # Load configuration
        if HAS_CONFIG and os.path.exists(config_path):
            self.config = NovaLangConfig(config_path)
            db_config = self.config.get_database_config()
            self.db_type = db_config['type'].lower()
            self.db_config = db_config
        else:
            self.config = None
            self.db_type = 'sqlite'
            self.db_config = {'name': 'novalang.db'}
        
        # Built-in functions
        self._setup_builtins()
    
    def _setup_builtins(self):
        """Setup built-in functions"""
        def print_func(*args):
            print(*args)
            return None
        
        def len_func(obj):
            return len(obj)
        
        def str_func(obj):
            return str(obj)
        
        def int_func(obj):
            return int(obj)
        
        def float_func(obj):
            return float(obj)
        
        self.global_scope.set('print', print_func)
        self.global_scope.set('len', len_func)
        self.global_scope.set('str', str_func)
        self.global_scope.set('int', int_func)
        self.global_scope.set('float', float_func)
    
    def execute_code(self, code: str) -> Any:
        """Execute NovaLang code"""
        lines = code.split('\n')
        return self._execute_lines(lines)
    
    def _execute_lines(self, lines: List[str]) -> Any:
        """Execute multiple lines of code"""
        i = 0
        result = None
        
        while i < len(lines):
            line = lines[i].strip()
            
            if not line or line.startswith('//'):
                i += 1
                continue
            
            # Handle class definitions
            if line.startswith('@') or (i + 1 < len(lines) and lines[i + 1].strip().startswith('class ')):
                i = self._handle_class_definition(lines, i)
                continue
            
            # Handle function definitions
            if line.startswith('function '):
                i = self._handle_function_definition(lines, i)
                continue
            
            # Handle variable declarations
            if line.startswith('let ') or line.startswith('const ') or line.startswith('var '):
                self._handle_variable_declaration(line)
                i += 1
                continue
            
            # Handle assignments
            if '=' in line and not line.startswith('//') and '==' not in line:
                self._handle_assignment(line)
                i += 1
                continue
            
            # Handle if statements
            if line.startswith('if '):
                i = self._handle_if_statement(lines, i)
                continue
            
            # Handle while loops
            if line.startswith('while '):
                i = self._handle_while_loop(lines, i)
                continue
            
            # Handle for loops
            if line.startswith('for '):
                i = self._handle_for_loop(lines, i)
                continue
            
            # Handle return statements
            if line.startswith('return '):
                return self._evaluate_expression(line[7:].strip())
            
            # Handle function calls
            if '(' in line and ')' in line:
                result = self._evaluate_expression(line)
                i += 1
                continue
            
            i += 1
        
        return result
    
    def _handle_class_definition(self, lines: List[str], start: int) -> int:
        """Handle class definition with annotations"""
        annotations = []
        i = start
        
        # Collect annotations
        while i < len(lines) and lines[i].strip().startswith('@'):
            ann_line = lines[i].strip()
            annotations.append(self._parse_annotation(ann_line))
            i += 1
        
        # Parse class line
        class_line = lines[i].strip()
        if not class_line.startswith('class '):
            return i + 1
        
        class_name = class_line.split()[1].split('{')[0].strip()
        
        # Find class body
        brace_count = 0
        class_body_start = i
        for j in range(i, len(lines)):
            brace_count += lines[j].count('{') - lines[j].count('}')
            if brace_count == 0 and j > i:
                class_body_end = j
                break
        else:
            class_body_end = len(lines)
        
        # Create class object
        nova_class = NovaClass(
            name=class_name,
            properties={},
            methods={},
            annotations=annotations
        )
        
        self.classes[class_name] = nova_class
        
        # Handle database entity classes
        if any(ann['name'] == 'DatabaseEntity' for ann in annotations):
            self._create_database_table(nova_class, lines[class_body_start:class_body_end + 1])
        
        return class_body_end + 1
    
    def _parse_annotation(self, annotation_line: str) -> Dict[str, Any]:
        """Parse annotation"""
        annotation_line = annotation_line[1:]  # Remove @
        if '(' in annotation_line:
            name = annotation_line.split('(')[0]
            params_str = annotation_line.split('(')[1].rstrip(')')
            params = {}
            # Simple param parsing
            for pair in params_str.split(','):
                if ':' in pair:
                    key, val = pair.split(':', 1)
                    params[key.strip()] = val.strip().strip('"\'')
            return {'name': name, 'params': params}
        return {'name': annotation_line, 'params': {}}
    
    def _create_database_table(self, nova_class: NovaClass, class_body: List[str]):
        """Create actual database table from annotations"""
        # Get table name
        table_name = nova_class.name.lower()
        for ann in nova_class.annotations:
            if ann['name'] == 'Table' and 'name' in ann['params']:
                table_name = ann['params']['name']
        
        # Parse properties to build CREATE TABLE
        columns = []
        primary_key = None
        col_annotations = {}
        
        i = 0
        while i < len(class_body):
            line = class_body[i].strip()
            
            # Collect annotations for next property
            if line.startswith('@'):
                ann = self._parse_annotation(line)
                # Store for next property
                i += 1
                if i < len(class_body):
                    next_line = class_body[i].strip()
                    if next_line.startswith('@'):
                        continue
                    elif 'let ' in next_line or 'const ' in next_line:
                        # This is the property line
                        prop_name = next_line.replace('let ', '').replace('const ', '').split(':')[0].strip()
                        
                        if ann['name'] == 'Column':
                            col_type = ann['params'].get('type', 'TEXT')
                            columns.append(f"{prop_name} {col_type}")
                        
                        if ann['name'] =='PrimaryKey':
                            primary_key = prop_name
                continue
            
            i += 1
        
        # Create table in SQLite
        if columns:
            self._ensure_db_connection()
            
            # Add PRIMARY KEY constraint if specified
            if primary_key:
                # Find and modify the primary key column
                for idx, col in enumerate(columns):
                    if col.startswith(primary_key + ' '):
                        columns[idx] = col + ' PRIMARY KEY'
                        break
            
            create_sql = f"CREATE TABLE IF NOT EXISTS {table_name} ({', '.join(columns)})"
            
            try:
                cursor = self.db_connection.cursor()
                cursor.execute(create_sql)
                self.db_connection.commit()
                cursor.close()
                print(f"[OK] Created table: {table_name}")
            except Exception as e:
                print(f"[ERROR] Database error: {e}")
                print(f"[DEBUG] SQL was: {create_sql}")
    
    def _ensure_db_connection(self):
        """Ensure database connection exists"""
        if not self.db_connection:
            if self.db_type == 'mysql':
                if not HAS_MYSQL:
                    print("⚠️  MySQL support not available. Install: pip install mysql-connector-python")
                    print("   Falling back to SQLite")
                    self.db_type = 'sqlite'
                    self.db_connection = sqlite3.connect('novalang_fallback.db')
                else:
                    try:
                        self.db_connection = mysql.connector.connect(
                            host=self.db_config.get('host', 'localhost'),
                            port=self.db_config.get('port', 3306),
                            user=self.db_config.get('username', 'root'),
                            password=self.db_config.get('password', ''),
                            database=self.db_config.get('name', 'novalang')
                        )
                        print(f"📦 Connected to MySQL database: {self.db_config.get('name')}@{self.db_config.get('host')}")
                    except Exception as e:
                        print(f"⚠️  MySQL connection failed: {e}")
                        print("   Falling back to SQLite")
                        self.db_type = 'sqlite'
                        self.db_connection = sqlite3.connect('novalang_fallback.db')
            else:
                # SQLite (default)
                db_name = self.db_config.get('name', 'novalang.db')
                self.db_connection = sqlite3.connect(db_name)
                print(f"📦 Connected to SQLite database: {db_name}")
    
    def _handle_function_definition(self, lines: List[str], start: int) -> int:
        """Handle function definition"""
        func_line = lines[start].strip()
        
        # Parse: function name(params) {
        name = func_line.split('(')[0].replace('function ', '').strip()
        params_str = func_line.split('(')[1].split(')')[0]
        # Parse parameters, removing type annotations
        parameters = []
        for p in params_str.split(','):
            p = p.strip()
            if p:
                # Remove type annotation (e.g., "a: number" -> "a")
                param_name = p.split(':')[0].strip()
                parameters.append(param_name)
        
        # Find function body
        brace_count = 0
        func_body = []
        i = start
        
        for j in range(start, len(lines)):
            line = lines[j]
            brace_count += line.count('{') - line.count('}')
            if j > start:
                func_body.append(line)
            if brace_count == 0 and j > start:
                break
        
        # Store function
        self.functions[name] = NovaFunction(
            name=name,
            parameters=parameters,
            body=func_body,
            scope=self.current_scope
        )
        
        return j + 1
    
    def _handle_variable_declaration(self, line: str):
        """Handle variable declaration"""
        # let name: type = value
        line = line.replace('let ', '').replace('const ', '').replace('var ', '')
        
        if '=' in line:
            name_part, value_part = line.split('=', 1)
            name = name_part.split(':')[0].strip()
            value = self._evaluate_expression(value_part.strip())
            self.current_scope.set(name, value)
        else:
            name = line.split(':')[0].strip()
            self.current_scope.set(name, None)
    
    def _handle_assignment(self, line: str):
        """Handle variable assignment"""
        if '=' in line:
            name, value_part = line.split('=', 1)
            name = name.strip()
            value = self._evaluate_expression(value_part.strip())
            self.current_scope.set(name, value)
    
    def _handle_if_statement(self, lines: List[str], start: int) -> int:
        """Handle if statement"""
        if_line = lines[start].strip()
        condition_str = if_line.replace('if ', '').replace('(', '').replace(')', '').replace('{', '').strip()
        condition = self._evaluate_expression(condition_str)
        
        # Find if body
        brace_count = 0
        if_body = []
        i = start
        
        for j in range(start, len(lines)):
            line = lines[j]
            brace_count += line.count('{') - line.count('}')
            if j > start and brace_count > 0:
                if_body.append(line)
            if brace_count == 0 and j > start:
                i = j
                break
        
        # Execute if body if condition is true
        if condition:
            self._execute_lines(if_body)
        
        # Check for else
        if i + 1 < len(lines) and lines[i + 1].strip().startswith('else'):
            i += 1
            # Find else body
            else_body = []
            brace_count = 0
            for j in range(i, len(lines)):
                line = lines[j]
                brace_count += line.count('{') - line.count('}')
                if j > i and brace_count > 0:
                    else_body.append(line)
                if brace_count == 0 and j > i:
                    i = j
                    break
            
            if not condition:
                self._execute_lines(else_body)
        
        return i + 1
    
    def _handle_while_loop(self, lines: List[str], start: int) -> int:
        """Handle while loop"""
        while_line = lines[start].strip()
        condition_str = while_line.replace('while ', '').replace('(', '').replace(')', '').replace('{', '').strip()
        
        # Find while body
        brace_count = 0
        while_body = []
        i = start
        
        for j in range(start, len(lines)):
            line = lines[j]
            brace_count += line.count('{') - line.count('}')
            if j > start and brace_count > 0:
                while_body.append(line)
            if brace_count == 0 and j > start:
                i = j
                break
        
        # Execute while body while condition is true
        max_iterations = 10000  # Safety limit
        iterations = 0
        while self._evaluate_expression(condition_str) and iterations < max_iterations:
            self._execute_lines(while_body)
            iterations += 1
        
        return i + 1
    
    def _handle_for_loop(self, lines: List[str], start: int) -> int:
        """Handle for loop"""
        for_line = lines[start].strip()
        # for (let i = 0; i < 10; i++) or for item in array
        
        # Find for body
        brace_count = 0
        for_body = []
        i = start
        
        for j in range(start, len(lines)):
            line = lines[j]
            brace_count += line.count('{') - line.count('}')
            if j > start and brace_count > 0:
                for_body.append(line)
            if brace_count == 0 and j > start:
                i = j
                break
        
        # Simple for loop: for (let i = 0; i < 10; i++)
        if '=' in for_line and ';' in for_line:
            parts = for_line.replace('for ', '').replace('(', '').replace(')', '').strip().split(';')
            if len(parts) == 3:
                init = parts[0].strip()
                condition = parts[1].strip()
                increment = parts[2].strip()
                
                # Execute init
                self._handle_variable_declaration(init)
                
                # Execute loop
                max_iterations = 10000
                iterations = 0
                while self._evaluate_expression(condition) and iterations < max_iterations:
                    self._execute_lines(for_body)
                    # Execute increment
                    if '++' in increment:
                        var_name = increment.replace('++', '').strip()
                        current = self.current_scope.get(var_name)
                        self.current_scope.set(var_name, current + 1)
                    else:
                        self._evaluate_expression(increment)
                    iterations += 1
        
        return i + 1
    
    def _evaluate_expression(self, expr: str) -> Any:
        """Evaluate an expression"""
        expr = expr.strip()
        
        if not expr:
            return None
        
        # String literals
        if (expr.startswith('"') and expr.endswith('"')) or (expr.startswith("'") and expr.endswith("'")):
            return expr[1:-1]
        
        # Number literals
        if expr.replace('.', '').replace('-', '').isdigit():
            return float(expr) if '.' in expr else int(expr)
        
        # Boolean literals
        if expr == 'true':
            return True
        if expr == 'false':
            return False
        if expr == 'null':
            return None
        
        # Function calls
        if '(' in expr and ')' in expr:
            func_name = expr.split('(')[0].strip()
            args_str = expr.split('(')[1].rsplit(')', 1)[0]
            args = [self._evaluate_expression(arg.strip()) for arg in args_str.split(',') if arg.strip()]
            
            # Built-in functions
            if self.current_scope.exists(func_name):
                func = self.current_scope.get(func_name)
                if callable(func):
                    return func(*args)
            
            # User-defined functions
            if func_name in self.functions:
                return self._call_function(self.functions[func_name], args)
        
        # Binary operations
        if ' + ' in expr:
            left, right = expr.split(' + ', 1)
            left_val = self._evaluate_expression(left)
            right_val = self._evaluate_expression(right)
            # String concatenation - convert to string if either is string
            if isinstance(left_val, str) or isinstance(right_val, str):
                return str(left_val) + str(right_val)
            return left_val + right_val
        if ' - ' in expr:
            left, right = expr.split(' - ', 1)
            return self._evaluate_expression(left) - self._evaluate_expression(right)
        if ' * ' in expr:
            left, right = expr.split(' * ', 1)
            return self._evaluate_expression(left) * self._evaluate_expression(right)
        if ' / ' in expr:
            left, right = expr.split(' / ', 1)
            return self._evaluate_expression(left) / self._evaluate_expression(right)
        
        # Comparisons
        if ' == ' in expr:
            left, right = expr.split(' == ', 1)
            return self._evaluate_expression(left) == self._evaluate_expression(right)
        if ' != ' in expr:
            left, right = expr.split(' != ', 1)
            return self._evaluate_expression(left) != self._evaluate_expression(right)
        if ' < ' in expr:
            left, right = expr.split(' < ', 1)
            return self._evaluate_expression(left) < self._evaluate_expression(right)
        if ' > ' in expr:
            left, right = expr.split(' > ', 1)
            return self._evaluate_expression(left) > self._evaluate_expression(right)
        if ' <= ' in expr:
            left, right = expr.split(' <= ', 1)
            return self._evaluate_expression(left) <= self._evaluate_expression(right)
        if ' >= ' in expr:
            left, right = expr.split(' >= ', 1)
            return self._evaluate_expression(left) >= self._evaluate_expression(right)
        
        # Logical operations
        if ' && ' in expr:
            left, right = expr.split(' && ', 1)
            return self._evaluate_expression(left) and self._evaluate_expression(right)
        if ' || ' in expr:
            left, right = expr.split(' || ', 1)
            return self._evaluate_expression(left) or self._evaluate_expression(right)
        
        # Increment/decrement
        if expr.endswith('++'):
            var_name = expr[:-2].strip()
            value = self.current_scope.get(var_name)
            self.current_scope.set(var_name, value + 1)
            return value + 1
        if expr.endswith('--'):
            var_name = expr[:-2].strip()
            value = self.current_scope.get(var_name)
            self.current_scope.set(var_name, value - 1)
            return value - 1
        
        # Variable reference
        if self.current_scope.exists(expr):
            return self.current_scope.get(expr)
        
        # Try to evaluate as Python expression (fallback)
        try:
            return eval(expr, {"__builtins__": {}}, self.current_scope.variables)
        except:
            return expr
    
    def _call_function(self, func: NovaFunction, args: List[Any]) -> Any:
        """Call a user-defined function"""
        # Create new scope for function
        func_scope = Scope(parent=func.scope)
        
        # Bind parameters
        for i, param in enumerate(func.parameters):
            if i < len(args):
                func_scope.set(param, args[i])
        
        # Save current scope
        old_scope = self.current_scope
        self.current_scope = func_scope
        
        # Execute function body
        result = self._execute_lines(func.body)
        
        # Restore scope
        self.current_scope = old_scope
        
        return result
    
    def scan_and_create_entities(self, directory: str = "."):
        """Scan workspace for @DatabaseEntity classes and create tables"""
        print(f"🔍 Scanning for entities in {directory}...")
        
        # Find all .nova files
        nova_files = glob.glob(os.path.join(directory, "*.nova"))
        if not nova_files:
            print("⚠️  No .nova files found")
            return
        
        entities_found = 0
        tables_created = 0
        
        for file_path in nova_files:
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    content = f.read()
                
                # Check if file contains @DatabaseEntity
                if '@DatabaseEntity' in content:
                    entities_found += 1
                    # Parse and create tables
                    if self._process_entity_file(content):
                        tables_created += 1
                        
            except Exception as e:
                print(f"⚠️  Error processing {file_path}: {e}")
        
        if entities_found > 0:
            print(f"✅ Found {entities_found} entities, created {tables_created} tables")
        else:
            print("ℹ️  No database entities found")
    
    def _process_entity_file(self, content: str) -> bool:
        """Process a file containing entity definitions"""
        lines = content.split('\n')
        
        for i, line in enumerate(lines):
            if '@DatabaseEntity' in line:
                # Collect annotations
                annotations = []
                j = i
                while j < len(lines) and (lines[j].strip().startswith('@') or not lines[j].strip()):
                    if lines[j].strip().startswith('@'):
                        ann = self._parse_annotation(lines[j].strip())
                        annotations.append(ann)
                    j += 1
                
                # Find class definition
                if j < len(lines) and 'class ' in lines[j]:
                    class_line = lines[j].strip()
                    class_name = class_line.replace('class ', '').split('{')[0].strip()
                    
                    # Find class body
                    class_body = []
                    brace_count = 0
                    for k in range(j, len(lines)):
                        brace_count += lines[k].count('{') - lines[k].count('}')
                        class_body.append(lines[k])
                        if brace_count == 0 and k > j:
                            break
                    
                    # Create class object
                    nova_class = NovaClass(
                        name=class_name,
                        properties={},
                        methods={},
                        annotations=annotations
                    )
                    
                    # Create table
                    self._create_database_table(nova_class, class_body)
                    return True
        
        return False
    
    def start_server(self):
        """Start HTTP server (placeholder for now)"""
        if self.config:
            server_config = self.config.get_server_config()
            host = server_config['host']
            port = server_config['port']
            print(f"🌐 Server would start on http://{host}:{port}")
            print("   (HTTP server implementation coming soon)")
        else:
            print("🌐 Server would start on http://localhost:8080")
            print("   (HTTP server implementation coming soon)")
    
    def cleanup(self):
        """Cleanup resources"""
        if self.db_connection:
            self.db_connection.close()
            print("📦 Database connection closed")

def main():
    """Test the runtime"""
    runtime = NovaLangRuntime()
    
    test_code = """
    let x = 5
    let y = 10
    let sum = x + y
    print("Sum:", sum)
    
    function add(a, b) {
        return a + b
    }
    
    let result = add(20, 30)
    print("Result:", result)
    
    for (let i = 0; i < 3; i++) {
        print("Loop:", i)
    }
    """
    
    runtime.execute_code(test_code)
    runtime.cleanup()

if __name__ == "__main__":
    main()
