"""
Unit tests for new QueryBuilder API.
"""

import unittest
import sys
import os
from pathlib import Path

# Add src directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))

from bmd.parser import Block, parse_blockmd
from bmd.query import query, QueryBuilder


class TestQueryBuilderBasics(unittest.TestCase):
    """Test basic QueryBuilder functionality."""
    
    def setUp(self):
        """Set up test data."""
        text = """# Project
`type`: project
`status`: active

Project description.

## Module A
`type`: module
`status`: complete

Module A content.

### Feature 1
`status`: complete
`priority`: high

Feature 1 details.

### Feature 2
`status`: in-progress
`priority`: low

Feature 2 details.

## Module B
`type`: module
`status`: in-progress

Module B content.
"""
        self.roots = parse_blockmd(text)
    
    def test_from_blocks(self):
        """Test initializing from blocks."""
        q = query().from_blocks(self.roots)
        self.assertEqual(q.count(), 5)
    
    def test_count(self):
        """Test counting blocks."""
        q = query().from_blocks(self.roots)
        self.assertEqual(q.count(), 5)
    
    def test_execute(self):
        """Test executing query."""
        blocks = query().from_blocks(self.roots).execute()
        self.assertEqual(len(blocks), 5)
        self.assertIsInstance(blocks[0], Block)


class TestQueryBuilderWhere(unittest.TestCase):
    """Test where clause filtering."""
    
    def setUp(self):
        """Set up test data."""
        text = """# Block 1
`status`: complete
`priority`: high
`level`: 1

## Block 2
`status`: in-progress
`priority`: medium

## Block 3
`status`: complete
`priority`: low

# Block 4
`status`: cancelled
"""
        self.roots = parse_blockmd(text)
    
    def test_where_exact_match(self):
        """Test exact property match."""
        results = query().from_blocks(self.roots).where(status='complete').execute()
        self.assertEqual(len(results), 2)
        for block in results:
            self.assertEqual(block.get_property('status'), 'complete')
    
    def test_where_level(self):
        """Test filtering by level."""
        results = query().from_blocks(self.roots).where(level=2).execute()
        self.assertEqual(len(results), 2)
        for block in results:
            self.assertEqual(block.level, 2)
    
    def test_where_lambda(self):
        """Test lambda condition."""
        results = query().from_blocks(self.roots).where(
            level=lambda l: l >= 2
        ).execute()
        self.assertTrue(len(results) >= 2)
        for block in results:
            self.assertGreaterEqual(block.level, 2)
    
    def test_where_list_values(self):
        """Test filtering with list of values."""
        results = query().from_blocks(self.roots).where(
            status=['complete', 'in-progress']
        ).execute()
        self.assertEqual(len(results), 3)
    
    def test_where_multiple_conditions(self):
        """Test multiple where conditions."""
        results = query().from_blocks(self.roots).where(
            status='complete',
            priority='high'
        ).execute()
        self.assertEqual(len(results), 1)
        self.assertEqual(results[0].title, 'Block 1')


class TestQueryBuilderHasProperty(unittest.TestCase):
    """Test has_property filtering."""
    
    def setUp(self):
        """Set up test data."""
        text = """# Block 1
`status`: complete

## Block 2
`priority`: high

## Block 3
`status`: in-progress
`priority`: low

# Block 4
"""
        self.roots = parse_blockmd(text)
    
    def test_has_property_single(self):
        """Test checking single property existence."""
        results = query().from_blocks(self.roots).has_property('status').execute()
        self.assertEqual(len(results), 2)
    
    def test_has_property_multiple(self):
        """Test checking multiple properties."""
        results = query().from_blocks(self.roots).has_property(
            'status', 'priority'
        ).execute()
        self.assertEqual(len(results), 1)
        self.assertEqual(results[0].title, 'Block 3')
    
    def test_has_property_none(self):
        """Test block with no properties."""
        results = query().from_blocks(self.roots).has_property('nonexistent').execute()
        self.assertEqual(len(results), 0)


class TestQueryBuilderContains(unittest.TestCase):
    """Test content search."""
    
    def setUp(self):
        """Set up test data."""
        text = """# Block 1

This contains TODO items.

## Block 2

Nothing special here.

## Block 3

Important TODO: Fix bug.
"""
        self.roots = parse_blockmd(text)
    
    def test_contains_case_insensitive(self):
        """Test case-insensitive search."""
        results = query().from_blocks(self.roots).contains('TODO').execute()
        self.assertEqual(len(results), 2)
    
    def test_contains_case_sensitive(self):
        """Test case-sensitive search."""
        results = query().from_blocks(self.roots).contains(
            'TODO', case_sensitive=True
        ).execute()
        self.assertEqual(len(results), 2)
        
        # Should not match 'todo'
        results = query().from_blocks(self.roots).contains(
            'todo', case_sensitive=True
        ).execute()
        self.assertEqual(len(results), 0)
    
    def test_contains_no_match(self):
        """Test search with no matches."""
        results = query().from_blocks(self.roots).contains('FIXME').execute()
        self.assertEqual(len(results), 0)


class TestQueryBuilderFilter(unittest.TestCase):
    """Test custom filter predicates."""
    
    def setUp(self):
        """Set up test data."""
        text = """# Block 1

Short.

## Block 2

This is a longer content block with multiple sentences.

## Block 3

### Block 4

Very very long content here that goes on and on with lots of text.
"""
        self.roots = parse_blockmd(text)
    
    def test_filter_by_content_length(self):
        """Test filtering by content length."""
        results = query().from_blocks(self.roots).filter(
            lambda b: len(b.get_content_text()) > 30
        ).execute()
        self.assertTrue(len(results) >= 1)
    
    def test_filter_by_children_count(self):
        """Test filtering by number of children."""
        results = query().from_blocks(self.roots).filter(
            lambda b: len(b.children) > 0
        ).execute()
        self.assertTrue(len(results) >= 1)
    
    def test_filter_is_leaf(self):
        """Test filtering leaf nodes."""
        results = query().from_blocks(self.roots).filter(
            lambda b: b.is_leaf()
        ).execute()
        self.assertTrue(len(results) >= 2)


class TestQueryBuilderOrderBy(unittest.TestCase):
    """Test sorting functionality."""
    
    def setUp(self):
        """Set up test data."""
        text = """# Zebra
`priority`: 3

## Apple
`priority`: 1

## Banana
`priority`: 2

# Cat
`priority`: 4
"""
        self.roots = parse_blockmd(text)
    
    def test_order_by_title(self):
        """Test sorting by title."""
        results = query().from_blocks(self.roots).order_by('title').execute()
        titles = [b.title for b in results]
        self.assertEqual(titles, ['Apple', 'Banana', 'Cat', 'Zebra'])
    
    def test_order_by_title_reverse(self):
        """Test reverse sorting by title."""
        results = query().from_blocks(self.roots).order_by(
            'title', reverse=True
        ).execute()
        titles = [b.title for b in results]
        self.assertEqual(titles, ['Zebra', 'Cat', 'Banana', 'Apple'])
    
    def test_order_by_level(self):
        """Test sorting by level."""
        results = query().from_blocks(self.roots).order_by('level').execute()
        levels = [b.level for b in results]
        # Should have 1s before 2s
        self.assertTrue(levels.index(1) < levels.index(2))
    
    def test_order_by_property(self):
        """Test sorting by property."""
        results = query().from_blocks(self.roots).order_by('priority').execute()
        priorities = [b.get_property('priority', '') for b in results]
        self.assertEqual(priorities, ['1', '2', '3', '4'])
    
    def test_order_by_lambda(self):
        """Test sorting by lambda function."""
        results = query().from_blocks(self.roots).order_by(
            lambda b: len(b.title)
        ).execute()
        lengths = [len(b.title) for b in results]
        # Should be sorted by title length
        self.assertEqual(lengths, sorted(lengths))


class TestQueryBuilderLimit(unittest.TestCase):
    """Test limit functionality."""
    
    def setUp(self):
        """Set up test data."""
        text = """# Block 1
## Block 2
## Block 3
## Block 4
## Block 5
"""
        self.roots = parse_blockmd(text)
    
    def test_limit(self):
        """Test limiting results."""
        results = query().from_blocks(self.roots).limit(3).execute()
        self.assertEqual(len(results), 3)
    
    def test_limit_larger_than_results(self):
        """Test limit larger than result count."""
        results = query().from_blocks(self.roots).limit(100).execute()
        self.assertEqual(len(results), 5)
    
    def test_limit_zero(self):
        """Test limit of zero."""
        results = query().from_blocks(self.roots).limit(0).execute()
        self.assertEqual(len(results), 0)


class TestQueryBuilderSelect(unittest.TestCase):
    """Test field selection."""
    
    def setUp(self):
        """Set up test data."""
        text = """# Project
`type`: project
`status`: active

Content here.

## Module
`type`: module
"""
        self.roots = parse_blockmd(text)
    
    def test_select_title(self):
        """Test selecting title field."""
        results = query().from_blocks(self.roots).select('title')
        self.assertEqual(len(results), 2)
        self.assertIn('title', results[0])
        self.assertEqual(results[0]['title'], 'Project')
    
    def test_select_multiple_fields(self):
        """Test selecting multiple fields."""
        results = query().from_blocks(self.roots).select('title', 'level', 'type')
        self.assertEqual(len(results), 2)
        self.assertIn('title', results[0])
        self.assertIn('level', results[0])
        self.assertIn('type', results[0])
    
    def test_select_property(self):
        """Test selecting property fields."""
        results = query().from_blocks(self.roots).where(level=1).select('title', 'status')
        self.assertEqual(len(results), 1)
        self.assertEqual(results[0]['status'], 'active')


class TestQueryBuilderChaining(unittest.TestCase):
    """Test method chaining."""
    
    def setUp(self):
        """Set up test data."""
        text = """# Project A
`status`: complete
`priority`: high

Content A.

## Module A1
`status`: complete

Content with TODO.

## Module A2
`status`: in-progress

# Project B
`status`: in-progress
`priority`: low

## Module B1
`status`: complete

Content B1.
"""
        self.roots = parse_blockmd(text)
    
    def test_chain_where_and_order(self):
        """Test chaining where and order_by."""
        results = query().from_blocks(self.roots).where(
            status='complete'
        ).order_by('title').execute()
        
        titles = [b.title for b in results]
        self.assertEqual(titles, sorted(titles))
    
    def test_chain_where_and_limit(self):
        """Test chaining where and limit."""
        results = query().from_blocks(self.roots).where(
            level=2
        ).limit(2).execute()
        
        self.assertEqual(len(results), 2)
    
    def test_chain_contains_where_order(self):
        """Test complex chain."""
        results = query().from_blocks(self.roots).contains(
            'Content'
        ).where(
            level=2
        ).order_by('title').execute()
        
        self.assertTrue(len(results) >= 1)
        for block in results:
            self.assertEqual(block.level, 2)
            self.assertIn('Content', block.get_content_text())
    
    def test_chain_filter_has_property_select(self):
        """Test chain with filter, has_property, and select."""
        results = query().from_blocks(self.roots).filter(
            lambda b: b.level <= 2
        ).has_property('status').select('title', 'status')
        
        self.assertTrue(len(results) >= 1)
        for item in results:
            self.assertIn('title', item)
            self.assertIn('status', item)


class TestQueryBuilderViews(unittest.TestCase):
    """Test view formatting."""
    
    def setUp(self):
        """Set up test data."""
        text = """# Root
`type`: root

## Child A
`type`: child

### Grandchild
`type`: grandchild

## Child B
`type`: child
"""
        self.roots = parse_blockmd(text)
    
    def test_view_list(self):
        """Test list view."""
        output = query().from_blocks(self.roots).view('list')
        self.assertIsInstance(output, str)
        self.assertIn('Root', output)
        self.assertIn('Child A', output)
    
    def test_view_tree(self):
        """Test tree view."""
        output = query().from_blocks(self.roots).view('tree')
        self.assertIsInstance(output, str)
        # Should contain tree characters
        self.assertTrue('└──' in output or '├──' in output)
    
    def test_view_table(self):
        """Test table view."""
        output = query().from_blocks(self.roots).view('table', fields=['title', 'level'])
        self.assertIsInstance(output, str)
        # Should contain table separator
        self.assertIn('-+-', output)
        self.assertIn('title', output)
        self.assertIn('level', output)
    
    def test_view_list_with_fields(self):
        """Test list view with custom fields."""
        output = query().from_blocks(self.roots).view('list', fields=['title', 'type'])
        self.assertIn('Root', output)
        self.assertIn('type=', output)


def run_tests():
    """Run all QueryBuilder tests."""
    loader = unittest.TestLoader()
    suite = unittest.TestSuite()
    
    suite.addTests(loader.loadTestsFromTestCase(TestQueryBuilderBasics))
    suite.addTests(loader.loadTestsFromTestCase(TestQueryBuilderWhere))
    suite.addTests(loader.loadTestsFromTestCase(TestQueryBuilderHasProperty))
    suite.addTests(loader.loadTestsFromTestCase(TestQueryBuilderContains))
    suite.addTests(loader.loadTestsFromTestCase(TestQueryBuilderFilter))
    suite.addTests(loader.loadTestsFromTestCase(TestQueryBuilderOrderBy))
    suite.addTests(loader.loadTestsFromTestCase(TestQueryBuilderLimit))
    suite.addTests(loader.loadTestsFromTestCase(TestQueryBuilderSelect))
    suite.addTests(loader.loadTestsFromTestCase(TestQueryBuilderChaining))
    suite.addTests(loader.loadTestsFromTestCase(TestQueryBuilderViews))
    
    runner = unittest.TextTestRunner(verbosity=2)
    result = runner.run(suite)
    
    return result.wasSuccessful()


if __name__ == '__main__':
    success = run_tests()
    sys.exit(0 if success else 1)
