#!/usr/bin/env python3
"""
Basic test suite for Minecraft Datapack Language (MDL)
Tests core functionality that actually works with our current implementation.
"""

import unittest
import tempfile
import os
import json
from pathlib import Path

# Import all MDL components
from minecraft_datapack_language.mdl_lexer_js import MDLLexer, TokenType
from minecraft_datapack_language.mdl_parser_js import parse_mdl_js
from minecraft_datapack_language.cli import _merge_mdl_files, _ast_to_pack


class TestBasicLexer(unittest.TestCase):
    """Test basic lexer functionality"""
    
    def test_basic_tokens(self):
        """Test basic token recognition"""
        lexer = MDLLexer()
        tokens = lexer.lex('pack "test" "description" 82;')
        
        # Check token types
        self.assertEqual(tokens[0].type, TokenType.PACK)
        self.assertEqual(tokens[1].type, TokenType.STRING)
        self.assertEqual(tokens[2].type, TokenType.STRING)
        self.assertEqual(tokens[3].type, TokenType.NUMBER)
        self.assertEqual(tokens[4].type, TokenType.SEMICOLON)
    
    def test_variable_substitution(self):
        """Test variable substitution tokenization"""
        lexer = MDLLexer()
        tokens = lexer.lex('counter = $counter$ + 1;')
        
        # Find variable substitution token
        var_tokens = [t for t in tokens if t.type == TokenType.VARIABLE_SUB]
        self.assertEqual(len(var_tokens), 1)
        self.assertEqual(var_tokens[0].value, 'counter')
    
    def test_strings(self):
        """Test string tokenization"""
        lexer = MDLLexer()
        tokens = lexer.lex('say "Hello, World!";')
        
        string_tokens = [t for t in tokens if t.type == TokenType.STRING]
        self.assertEqual(len(string_tokens), 1)
        self.assertEqual(string_tokens[0].value, '"Hello, World!"')  # Includes quotes


class TestBasicParser(unittest.TestCase):
    """Test basic parser functionality"""
    
    def test_pack_declaration(self):
        """Test pack declaration parsing"""
        code = 'pack "test" "description" 82;'
        ast = parse_mdl_js(code)
        
        self.assertIn('pack', ast)
        self.assertEqual(ast['pack']['name'], 'test')
        self.assertEqual(ast['pack']['description'], 'description')
        self.assertEqual(ast['pack']['pack_format'], 82)
    
    def test_namespace_declaration(self):
        """Test namespace declaration parsing"""
        code = 'namespace "test";'
        ast = parse_mdl_js(code)
        
        self.assertIn('namespace', ast)
        self.assertEqual(ast['namespace']['name'], 'test')
    
    def test_variable_declaration(self):
        """Test variable declaration parsing"""
        code = 'var num counter = 0;'
        ast = parse_mdl_js(code)
        
        self.assertIn('variables', ast)
        self.assertEqual(len(ast['variables']), 1)
        var = ast['variables'][0]
        self.assertEqual(var.name, 'counter')
        self.assertEqual(var.data_type, 'num')
        self.assertEqual(var.value.value, '0')
    
    def test_function_declaration(self):
        """Test function declaration parsing"""
        code = '''
        function "test_func" {
            say "Hello";
        }
        '''
        ast = parse_mdl_js(code)
        
        self.assertIn('functions', ast)
        self.assertEqual(len(ast['functions']), 1)
        func = ast['functions'][0]
        self.assertEqual(func['name'], 'test_func')
        self.assertIn('body', func)
    
    def test_on_load_on_tick(self):
        """Test on_load and on_tick parsing"""
        code = '''
        on_load "test:main";
        on_tick "test:tick";
        '''
        ast = parse_mdl_js(code)
        
        self.assertIn('hooks', ast)
        hooks = ast['hooks']
        self.assertEqual(len(hooks), 2)
        
        load_hooks = [h for h in hooks if h['hook_type'] == 'load']
        tick_hooks = [h for h in hooks if h['hook_type'] == 'tick']
        
        self.assertEqual(len(load_hooks), 1)
        self.assertEqual(len(tick_hooks), 1)
        self.assertEqual(load_hooks[0]['function_name'], 'test:main')
        self.assertEqual(tick_hooks[0]['function_name'], 'test:tick')


class TestBasicCLI(unittest.TestCase):
    """Test basic CLI functionality"""
    
    def test_ast_to_pack_conversion(self):
        """Test AST to Pack conversion"""
        ast = {
            'pack': {'name': 'test', 'description': 'Test pack', 'pack_format': 82},
            'namespace': {'name': 'test'},
            'variables': [],
            'functions': [],
            'hooks': [],
            'tags': [],
            'imports': [],
            'exports': [],
            'recipes': [],
            'loot_tables': [],
            'advancements': [],
            'predicates': [],
            'item_modifiers': [],
            'structures': []
        }
        
        # Create a temporary file for the mdl_files parameter
        with tempfile.NamedTemporaryFile(mode='w', suffix='.mdl', delete=False) as f:
            f.write('pack "test" "Test pack" 82;')
            f_path = f.name
        
        try:
            pack = _ast_to_pack(ast, [f_path])
            
            self.assertIsNotNone(pack)
            self.assertEqual(pack.name, 'test')
            self.assertEqual(pack.description, 'Test pack')
            self.assertEqual(pack.pack_format, 82)
        finally:
            os.unlink(f_path)


class TestBasicVariableSubstitution(unittest.TestCase):
    """Test basic variable substitution functionality"""
    
    def test_say_command_variable_substitution(self):
        """Test variable substitution in say commands"""
        code = '''
        pack "test" "description" 82;
        namespace "test";
        var num counter = 0;
        function "main" {
            say "Counter: $counter$";
        }
        '''
        
        ast = parse_mdl_js(code)
        
        # Create a temporary file for the mdl_files parameter
        with tempfile.NamedTemporaryFile(mode='w', suffix='.mdl', delete=False) as f:
            f.write(code)
            f_path = f.name
        
        try:
            pack = _ast_to_pack(ast, [f_path])
            
            # Check that the function was created
            self.assertIsNotNone(pack)
            
            # Check that the namespace was created
            namespace = pack.namespace('test')
            self.assertIsNotNone(namespace)
            
            # Check that the function was created
            function = namespace.function('main')
            self.assertIsNotNone(function)
            
            # Check the generated commands
            commands = function.commands
            self.assertGreater(len(commands), 0)
            
        finally:
            os.unlink(f_path)
    
    def test_variable_assignment_and_substitution(self):
        """Test variable assignment and substitution in the same function"""
        code = '''
        pack "test" "description" 82;
        namespace "test";
        var num counter = 0;
        function "main" {
            say "Initial counter: $counter$";
            counter = $counter$ + 1;
            say "After increment: $counter$";
            counter = $counter$ + 5;
            say "After adding 5: $counter$";
        }
        '''
        
        ast = parse_mdl_js(code)
        
        # Create a temporary file for the mdl_files parameter
        with tempfile.NamedTemporaryFile(mode='w', suffix='.mdl', delete=False) as f:
            f.write(code)
            f_path = f.name
        
        try:
            pack = _ast_to_pack(ast, [f_path])
            
            # Check that the function was created
            self.assertIsNotNone(pack)
            
            # Check that the namespace was created
            namespace = pack.namespace('test')
            self.assertIsNotNone(namespace)
            
            # Check that the function was created
            function = namespace.function('main')
            self.assertIsNotNone(function)
            
            # Check the generated commands
            commands = function.commands
            self.assertGreater(len(commands), 0)
            
            # Should have tellraw commands for the say statements
            tellraw_commands = [cmd for cmd in commands if cmd.startswith('tellraw')]
            self.assertGreater(len(tellraw_commands), 0)
            
            # Should have scoreboard commands for the variable assignments
            scoreboard_commands = [cmd for cmd in commands if cmd.startswith('scoreboard')]
            self.assertGreater(len(scoreboard_commands), 0)
            
        finally:
            os.unlink(f_path)

    def test_function_call_with_selector(self):
        """function name<@a>; should compile to execute as @a run function name"""
        code = '''
        pack "test" "description" 82;
        namespace "test";
        function "main" {
            function "test:hello<@a>";
        }
        '''
        ast = parse_mdl_js(code)
        with tempfile.NamedTemporaryFile(mode='w', suffix='.mdl', delete=False) as f:
            f.write(code)
            f_path = f.name
        try:
            pack = _ast_to_pack(ast, [f_path])
            self.assertIsNotNone(pack)
            namespace = pack.namespace('test')
            function = namespace.function('main')
            self.assertIsNotNone(function)
            commands = function.commands
            self.assertIn('execute as @a run function test:hello', commands)
        finally:
            os.unlink(f_path)


class TestBasicRegistryTypes(unittest.TestCase):
    """Test basic registry type functionality"""
    
    def test_recipe_registry(self):
        """Test recipe registry creation"""
        code = '''
        pack "test" "description" 82;
        namespace "test";
        recipe "test_recipe" "recipes/test_recipe.json";
        '''
        
        ast = parse_mdl_js(code)
        
        # Create a temporary file for the mdl_files parameter
        with tempfile.NamedTemporaryFile(mode='w', suffix='.mdl', delete=False) as f:
            f.write(code)
            f_path = f.name
        
        # Create the recipe JSON file
        recipe_dir = Path(f_path).parent / 'recipes'
        recipe_dir.mkdir(exist_ok=True)
        recipe_file = recipe_dir / 'test_recipe.json'
        with open(recipe_file, 'w') as f:
            json.dump({
                "type": "crafting_shaped",
                "pattern": ["X"],
                "key": {"X": {"item": "minecraft:diamond"}},
                "result": {"item": "minecraft:diamond_block"}
            }, f)
        
        try:
            pack = _ast_to_pack(ast, [f_path])
            
            self.assertIsNotNone(pack)
            namespace = pack.namespace('test')
            
            # Check that recipe was created
            self.assertGreater(len(namespace.recipes), 0)
            
        finally:
            os.unlink(f_path)
            if recipe_file.exists():
                os.unlink(recipe_file)
            if recipe_dir.exists():
                os.rmdir(recipe_dir)


class TestBasicErrorHandling(unittest.TestCase):
    """Test basic error handling functionality"""
    
    def test_missing_pack_declaration(self):
        """Test handling of missing pack declaration"""
        code = '''
        namespace "test";
        function "main" {
            say "Hello";
        }
        '''
        
        # Should not raise an error, but should handle gracefully
        ast = parse_mdl_js(code)
        self.assertIsNotNone(ast)
    
    def test_undefined_variables(self):
        """Test handling of undefined variables"""
        code = '''
        pack "test" "description" 82;
        namespace "test";
        function "main" {
            say "Value: $undefined_var$";
        }
        '''
        
        # Should handle gracefully
        ast = parse_mdl_js(code)
        self.assertIsNotNone(ast)


class TestBasicIntegration(unittest.TestCase):
    """Test basic integration scenarios"""
    
    def test_simple_datapack(self):
        """Test a simple datapack with basic features"""
        code = '''
        pack "simple_test" "Simple test pack" 82;
        namespace "test";
        
        var num counter = 0;
        
        function "main" {
            say "Hello from simple test";
            counter = $counter$ + 1;
            say "Counter: $counter$";
        }
        
        on_load "test:main";
        '''
        
        ast = parse_mdl_js(code)
        
        # Create a temporary file for the mdl_files parameter
        with tempfile.NamedTemporaryFile(mode='w', suffix='.mdl', delete=False) as f:
            f.write(code)
            f_path = f.name
        
        try:
            pack = _ast_to_pack(ast, [f_path])
            
            self.assertIsNotNone(pack)
            self.assertEqual(pack.name, 'simple_test')
            self.assertEqual(pack.description, 'Simple test pack')
            self.assertEqual(pack.pack_format, 82)
            
            namespace = pack.namespace('test')
            
            # Check that function was created
            self.assertGreater(len(namespace.functions), 0)
            
            # Check that pack has the correct format and description
            self.assertEqual(pack.pack_format, 82)
            self.assertEqual(pack.description, 'Simple test pack')
            
        finally:
            os.unlink(f_path)


def run_basic_tests():
    """Run all basic tests"""
    # Create test suite
    loader = unittest.TestLoader()
    suite = unittest.TestSuite()
    
    # Add all test classes
    test_classes = [
        TestBasicLexer,
        TestBasicParser,
        TestBasicCLI,
        TestBasicVariableSubstitution,
        TestBasicRegistryTypes,
        TestBasicErrorHandling,
        TestBasicIntegration
    ]
    
    for test_class in test_classes:
        tests = loader.loadTestsFromTestCase(test_class)
        suite.addTests(tests)
    
    # Run tests
    runner = unittest.TextTestRunner(verbosity=2)
    result = runner.run(suite)
    
    return result.wasSuccessful()


if __name__ == '__main__':
    success = run_basic_tests()
    exit(0 if success else 1)
