"""Tests for SQLite storage layer."""

import json
import tempfile
from pathlib import Path

import pytest

from tokennuke.storage.database import Database
from tokennuke.parser.symbols import Symbol, CallEdge


@pytest.fixture
def db():
    """Create a temporary database for testing."""
    with tempfile.TemporaryDirectory() as tmpdir:
        db_path = Path(tmpdir) / 'test.db'
        database = Database(db_path)
        yield database
        database.close()


@pytest.fixture
def sample_symbols():
    """Create sample symbols for testing."""
    return [
        Symbol(
            id='main.py::greet#function',
            file='main.py',
            name='greet',
            qualified_name='greet',
            kind='function',
            language='python',
            signature='def greet(name: str) -> str',
            docstring='Greet a user by name.',
            line=1,
            end_line=3,
            byte_offset=0,
            byte_length=50,
            content_hash='abc123',
        ),
        Symbol(
            id='main.py::UserService#class',
            file='main.py',
            name='UserService',
            qualified_name='UserService',
            kind='class',
            language='python',
            signature='class UserService',
            docstring='Manages users.',
            line=5,
            end_line=20,
            byte_offset=51,
            byte_length=200,
            content_hash='def456',
        ),
        Symbol(
            id='main.py::UserService.create#method',
            file='main.py',
            name='create',
            qualified_name='UserService.create',
            kind='method',
            language='python',
            signature='def create(self, name: str)',
            docstring='Create a new user.',
            parent='main.py::UserService#class',
            line=10,
            end_line=15,
            byte_offset=100,
            byte_length=80,
            content_hash='ghi789',
        ),
    ]


class TestDatabaseCRUD:
    def test_upsert_file(self, db, sample_symbols):
        file_id = db.upsert_file(
            path='main.py',
            sha256='filehash123',
            language='python',
            size_bytes=1000,
            content='def greet(name): pass\nclass UserService: pass',
            symbols=sample_symbols,
            call_edges={},
        )
        assert file_id > 0

    def test_get_file_hashes(self, db, sample_symbols):
        db.upsert_file(
            'main.py', 'hash1', 'python', 500, 'content',
            sample_symbols, {},
        )
        hashes = db.get_file_hashes()
        assert hashes == {'main.py': 'hash1'}

    def test_get_symbol(self, db, sample_symbols):
        db.upsert_file(
            'main.py', 'hash1', 'python', 500, 'content',
            sample_symbols, {},
        )
        sym = db.get_symbol('main.py::greet#function')
        assert sym is not None
        assert sym['name'] == 'greet'
        assert sym['kind'] == 'function'

    def test_get_symbol_by_qualified_name(self, db, sample_symbols):
        db.upsert_file(
            'main.py', 'hash1', 'python', 500, 'content',
            sample_symbols, {},
        )
        sym = db.get_symbol_by_qualified_name('UserService.create')
        assert sym is not None
        assert sym['kind'] == 'method'

    def test_get_symbols_batch(self, db, sample_symbols):
        db.upsert_file(
            'main.py', 'hash1', 'python', 500, 'content',
            sample_symbols, {},
        )
        results = db.get_symbols_batch([
            'main.py::greet#function',
            'main.py::UserService#class',
        ])
        assert len(results) == 2

    def test_file_outline(self, db, sample_symbols):
        db.upsert_file(
            'main.py', 'hash1', 'python', 500, 'content',
            sample_symbols, {},
        )
        outline = db.file_outline('main.py')
        assert len(outline) == 3
        assert outline[0]['line'] <= outline[1]['line']

    def test_repo_outline(self, db, sample_symbols):
        db.upsert_file(
            'main.py', 'hash1', 'python', 500, 'content',
            sample_symbols, {},
        )
        outline = db.repo_outline()
        assert len(outline) == 3

    def test_repo_outline_filtered(self, db, sample_symbols):
        db.upsert_file(
            'main.py', 'hash1', 'python', 500, 'content',
            sample_symbols, {},
        )
        outline = db.repo_outline(kind_filter='function')
        assert len(outline) == 1
        assert outline[0]['kind'] == 'function'

    def test_delete_file(self, db, sample_symbols):
        db.upsert_file(
            'main.py', 'hash1', 'python', 500, 'content',
            sample_symbols, {},
        )
        db.delete_file('main.py')
        assert db.get_symbol('main.py::greet#function') is None
        assert db.get_file_hashes() == {}

    def test_incremental_update(self, db, sample_symbols):
        db.upsert_file(
            'main.py', 'hash1', 'python', 500, 'content',
            sample_symbols, {},
        )
        # Same hash = would skip in real indexing
        hashes = db.get_file_hashes()
        assert hashes['main.py'] == 'hash1'

        # Update with new hash
        db.upsert_file(
            'main.py', 'hash2', 'python', 600, 'new content',
            sample_symbols[:1], {},
        )
        hashes = db.get_file_hashes()
        assert hashes['main.py'] == 'hash2'

        # Should only have 1 symbol now
        outline = db.file_outline('main.py')
        assert len(outline) == 1

    def test_stats(self, db, sample_symbols):
        db.upsert_file(
            'main.py', 'hash1', 'python', 500, 'content',
            sample_symbols, {},
        )
        stats = db.get_stats()
        assert stats['files'] == 1
        assert stats['symbols'] == 3
        assert 'python' in stats['languages']


class TestFTSSearch:
    def test_search_by_name(self, db, sample_symbols):
        db.upsert_file(
            'main.py', 'hash1', 'python', 500, 'content',
            sample_symbols, {},
        )
        results = db.search_symbols_fts('greet')
        assert len(results) > 0
        assert results[0]['name'] == 'greet'

    def test_search_by_docstring(self, db, sample_symbols):
        db.upsert_file(
            'main.py', 'hash1', 'python', 500, 'content',
            sample_symbols, {},
        )
        results = db.search_symbols_fts('user')
        assert len(results) > 0

    def test_search_with_kind_filter(self, db, sample_symbols):
        db.upsert_file(
            'main.py', 'hash1', 'python', 500, 'content',
            sample_symbols, {},
        )
        results = db.search_symbols_fts('user', kind='class')
        assert all(r['kind'] == 'class' for r in results)

    def test_search_text(self, db, sample_symbols):
        db.upsert_file(
            'main.py', 'hash1', 'python', 500,
            'def greet(name):\n    """Hello world"""\n    pass',
            sample_symbols, {},
        )
        results = db.search_text('Hello world')
        assert len(results) > 0
        assert results[0]['path'] == 'main.py'


class TestCallGraph:
    def test_call_edges_storage(self, db, sample_symbols):
        edges = {
            'main.py::greet#function': [
                CallEdge(callee_name='print', line=2),
                CallEdge(callee_name='format', line=3),
            ],
        }
        db.upsert_file(
            'main.py', 'hash1', 'python', 500, 'content',
            sample_symbols, edges,
        )
        stats = db.get_stats()
        assert stats['call_edges'] == 2

    def test_resolve_call_edges(self, db, sample_symbols):
        edges = {
            'main.py::greet#function': [
                CallEdge(callee_name='create', line=2),
            ],
        }
        db.upsert_file(
            'main.py', 'hash1', 'python', 500, 'content',
            sample_symbols, edges,
        )
        resolved = db.resolve_call_edges()
        assert resolved >= 0  # May or may not resolve depending on name matching

    def test_get_callees(self, db, sample_symbols):
        edges = {
            'main.py::greet#function': [
                CallEdge(callee_name='print', line=2),
            ],
        }
        db.upsert_file(
            'main.py', 'hash1', 'python', 500, 'content',
            sample_symbols, edges,
        )
        callees = db.get_callees('main.py::greet#function')
        assert len(callees) == 1
        assert callees[0]['callee_name'] == 'print'


class TestFileTree:
    def test_file_tree(self, db, sample_symbols):
        db.upsert_file(
            'src/main.py', 'hash1', 'python', 500, 'content',
            sample_symbols, {},
        )
        db.upsert_file(
            'src/utils.py', 'hash2', 'python', 300, 'content',
            [], {},
        )
        tree = db.file_tree()
        assert len(tree) > 0
        paths = [e['path'] for e in tree]
        assert any('src' in p for p in paths)

    def test_file_tree_prefix(self, db, sample_symbols):
        db.upsert_file(
            'src/main.py', 'hash1', 'python', 500, 'content',
            sample_symbols, {},
        )
        db.upsert_file(
            'tests/test_main.py', 'hash2', 'python', 300, 'content',
            [], {},
        )
        tree = db.file_tree(path_prefix='src/')
        file_entries = [e for e in tree if e['type'] == 'file']
        assert all('src' in e['path'] for e in file_entries)
