"""
Regression test for the "empty-cache early-return" bug in SymbolLibraryCommands.

Scenario
--------
When a project is opened for the first time (e.g. right after `create_project`)
the `sym-lib-table` may not exist on disk yet.  `SymbolLibraryManager.__init__`
succeeds but leaves `self.libraries` empty.

The original `_rebuild_if_needed` guard was:

    if self.library_manager.project_path == project_path:
        return   # ← BUG: triggers even when libraries == {}

Because `project_path` already matched, the manager was never rebuilt when the
sym-lib-table finally appeared, so subsequent list_symbols / search_symbols calls
returned nothing.

Fix: also require that at least one library was loaded before skipping the rebuild.
"""

import sys
import threading
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

sys.path.insert(0, str(Path(__file__).parent.parent / "python"))

from commands.library_symbol import SymbolLibraryCommands, SymbolLibraryManager


def _make_empty_manager(project_path) -> SymbolLibraryManager:
    """Return a SymbolLibraryManager that matches the project but has no libraries loaded."""
    manager = SymbolLibraryManager.__new__(SymbolLibraryManager)
    manager.project_path = project_path
    manager.libraries = {}        # <-- empty: sym-lib-table wasn't there yet
    manager.symbol_cache = {}
    manager._cache_lock = threading.Lock()
    return manager


@pytest.mark.unit
class TestSymbolLibraryEmptyCacheRebuild:
    """Ensure _rebuild_if_needed re-initialises the manager when the cache is empty."""

    def test_rebuild_triggered_when_libraries_empty(self):
        """
        If the manager has a matching project_path but no libraries, calling
        _rebuild_if_needed with the same path MUST trigger a rebuild (not early-return).
        """
        project_path = "/fake/project/test.kicad_pro"

        cmds = SymbolLibraryCommands.__new__(SymbolLibraryCommands)
        cmds.library_manager = _make_empty_manager(project_path)

        rebuild_count = {"n": 0}
        original_manager = cmds.library_manager

        def fake_init(self, project_path=None):
            rebuild_count["n"] += 1
            self.project_path = project_path
            self.libraries = {"SomeLib": "/fake/SomeLib.kicad_sym"}
            self.symbol_cache = {}
            self._cache_lock = threading.Lock()

        with patch.object(SymbolLibraryManager, "__init__", fake_init):
            cmds.use_project(Path(project_path))

        assert rebuild_count["n"] == 1, (
            "Expected use_project to construct a new SymbolLibraryManager "
            "when libraries={}, but it returned early instead."
        )
        assert cmds.library_manager is not original_manager, (
            "The manager instance should have been replaced after rebuild."
        )
        assert len(cmds.library_manager.libraries) > 0

    def test_no_rebuild_when_libraries_already_loaded(self):
        """
        When the manager already has libraries loaded for this project, skip rebuild
        (the original happy-path must still work).
        """
        project_path = "/fake/project/test.kicad_pro"

        cmds = SymbolLibraryCommands.__new__(SymbolLibraryCommands)
        manager = _make_empty_manager(Path(project_path))
        manager.libraries = {"SomeLib": "/fake/SomeLib.kicad_sym"}  # non-empty
        cmds.library_manager = manager
        original_manager = cmds.library_manager

        rebuild_count = {"n": 0}

        def fake_init(self, project_path=None):
            rebuild_count["n"] += 1

        with patch.object(SymbolLibraryManager, "__init__", fake_init):
            cmds.use_project(Path(project_path))

        assert rebuild_count["n"] == 0, (
            "Should NOT rebuild when libraries are already loaded for the same project."
        )
        assert cmds.library_manager is original_manager

    def test_rebuild_on_different_project_path(self):
        """Changing project path always triggers rebuild (existing behaviour)."""
        old_path = "/fake/project/old.kicad_pro"
        new_path = "/fake/project/new.kicad_pro"

        cmds = SymbolLibraryCommands.__new__(SymbolLibraryCommands)
        manager = _make_empty_manager(Path(old_path))
        manager.libraries = {"SomeLib": "/fake/SomeLib.kicad_sym"}
        cmds.library_manager = manager

        rebuild_count = {"n": 0}

        def fake_init(self, project_path=None):
            rebuild_count["n"] += 1
            self.project_path = project_path
            self.libraries = {}
            self.symbol_cache = {}
            self._cache_lock = threading.Lock()

        with patch.object(SymbolLibraryManager, "__init__", fake_init):
            cmds.use_project(Path(new_path))

        assert rebuild_count["n"] == 1, (
            "Switching to a different project path must always trigger rebuild."
        )
