"""
Tests for asset_heal_dialog module
"""

import pytest
import os
import shutil
from pathlib import Path
from unittest.mock import Mock, MagicMock, patch, call
from PyQt6.QtWidgets import QMessageBox, QFileDialog
from PyQt6.QtCore import Qt


class TestAssetHealDialog:
    """Tests for AssetHealDialog class"""

    @pytest.fixture
    def mock_project(self, tmp_path):
        """Create a mock project with folder_path"""
        project = Mock()
        project.folder_path = str(tmp_path / "project")
        os.makedirs(project.folder_path, exist_ok=True)

        # Create assets folder
        assets_path = os.path.join(project.folder_path, "assets")
        os.makedirs(assets_path, exist_ok=True)

        project.asset_manager = Mock()
        project.pages = []
        return project

    @pytest.fixture
    def mock_page_with_image(self):
        """Create a mock page with an image element"""
        from pyPhotoAlbum.models import ImageData

        page = Mock()
        element = Mock(spec=ImageData)
        element.image_path = "assets/image.jpg"
        page.layout = Mock()
        page.layout.elements = [element]
        return page

    def test_init(self, qtbot, mock_project):
        """Test AssetHealDialog initialization"""
        from pyPhotoAlbum.asset_heal_dialog import AssetHealDialog

        with patch.object(AssetHealDialog, '_scan_missing_assets'):
            dialog = AssetHealDialog(mock_project)
            qtbot.addWidget(dialog)

            assert dialog.project is mock_project
            assert dialog.search_paths == []
            assert dialog.missing_assets == set()
            assert dialog.windowTitle() == "Heal Missing Assets"

    def test_init_ui(self, qtbot, mock_project):
        """Test UI initialization"""
        from pyPhotoAlbum.asset_heal_dialog import AssetHealDialog

        with patch.object(AssetHealDialog, '_scan_missing_assets'):
            dialog = AssetHealDialog(mock_project)
            qtbot.addWidget(dialog)

            # Check that UI elements exist
            assert dialog.missing_list is not None
            assert dialog.search_list is not None

    def test_scan_missing_assets_no_missing(self, qtbot, mock_project, tmp_path):
        """Test scanning when no assets are missing"""
        from pyPhotoAlbum.asset_heal_dialog import AssetHealDialog
        from pyPhotoAlbum.models import ImageData

        # Create a valid image in assets folder
        img_path = os.path.join(mock_project.folder_path, "assets", "image.jpg")
        Path(img_path).touch()

        element = Mock(spec=ImageData)
        element.image_path = "assets/image.jpg"

        page = Mock()
        page.layout = Mock()
        page.layout.elements = [element]

        mock_project.pages = [page]

        dialog = AssetHealDialog(mock_project)
        qtbot.addWidget(dialog)

        assert len(dialog.missing_assets) == 0
        assert dialog.missing_list.count() == 1  # "No missing assets found!" message
        item = dialog.missing_list.item(0)
        assert "No missing assets" in item.text()

    def test_scan_missing_assets_absolute_path(self, qtbot, mock_project):
        """Test scanning detects absolute paths as needing healing"""
        from pyPhotoAlbum.asset_heal_dialog import AssetHealDialog
        from pyPhotoAlbum.models import ImageData

        element = Mock(spec=ImageData)
        element.image_path = "/absolute/path/to/image.jpg"

        page = Mock()
        page.layout = Mock()
        page.layout.elements = [element]

        mock_project.pages = [page]

        dialog = AssetHealDialog(mock_project)
        qtbot.addWidget(dialog)

        assert "/absolute/path/to/image.jpg" in dialog.missing_assets

    def test_scan_missing_assets_not_in_assets_folder(self, qtbot, mock_project):
        """Test scanning detects paths not in assets/ folder"""
        from pyPhotoAlbum.asset_heal_dialog import AssetHealDialog
        from pyPhotoAlbum.models import ImageData

        element = Mock(spec=ImageData)
        element.image_path = "images/photo.jpg"  # Not in assets/

        page = Mock()
        page.layout = Mock()
        page.layout.elements = [element]

        mock_project.pages = [page]

        dialog = AssetHealDialog(mock_project)
        qtbot.addWidget(dialog)

        assert "images/photo.jpg" in dialog.missing_assets

    def test_scan_missing_assets_file_missing(self, qtbot, mock_project):
        """Test scanning detects missing files in assets/ folder"""
        from pyPhotoAlbum.asset_heal_dialog import AssetHealDialog
        from pyPhotoAlbum.models import ImageData

        element = Mock(spec=ImageData)
        element.image_path = "assets/missing.jpg"  # File doesn't exist

        page = Mock()
        page.layout = Mock()
        page.layout.elements = [element]

        mock_project.pages = [page]

        dialog = AssetHealDialog(mock_project)
        qtbot.addWidget(dialog)

        assert "assets/missing.jpg" in dialog.missing_assets

    def test_scan_missing_assets_non_image_elements_ignored(self, qtbot, mock_project):
        """Test that non-ImageData elements are ignored"""
        from pyPhotoAlbum.asset_heal_dialog import AssetHealDialog

        # TextBox element (not ImageData)
        element = Mock()
        element.image_path = None

        page = Mock()
        page.layout = Mock()
        page.layout.elements = [element]

        mock_project.pages = [page]

        dialog = AssetHealDialog(mock_project)
        qtbot.addWidget(dialog)

        assert len(dialog.missing_assets) == 0

    def test_scan_missing_assets_empty_image_path(self, qtbot, mock_project):
        """Test that elements with empty image_path are ignored"""
        from pyPhotoAlbum.asset_heal_dialog import AssetHealDialog
        from pyPhotoAlbum.models import ImageData

        element = Mock(spec=ImageData)
        element.image_path = ""

        page = Mock()
        page.layout = Mock()
        page.layout.elements = [element]

        mock_project.pages = [page]

        dialog = AssetHealDialog(mock_project)
        qtbot.addWidget(dialog)

        assert len(dialog.missing_assets) == 0

    def test_add_search_path(self, qtbot, mock_project, tmp_path):
        """Test adding a search path"""
        from pyPhotoAlbum.asset_heal_dialog import AssetHealDialog

        with patch.object(AssetHealDialog, '_scan_missing_assets'):
            dialog = AssetHealDialog(mock_project)
            qtbot.addWidget(dialog)

            search_path = str(tmp_path / "search")
            os.makedirs(search_path, exist_ok=True)

            with patch.object(QFileDialog, 'getExistingDirectory', return_value=search_path):
                dialog._add_search_path()

            assert search_path in dialog.search_paths
            assert dialog.search_list.count() == 1

    def test_add_search_path_duplicate(self, qtbot, mock_project, tmp_path):
        """Test adding duplicate search path is ignored"""
        from pyPhotoAlbum.asset_heal_dialog import AssetHealDialog

        with patch.object(AssetHealDialog, '_scan_missing_assets'):
            dialog = AssetHealDialog(mock_project)
            qtbot.addWidget(dialog)

            search_path = str(tmp_path / "search")
            os.makedirs(search_path, exist_ok=True)

            with patch.object(QFileDialog, 'getExistingDirectory', return_value=search_path):
                dialog._add_search_path()
                dialog._add_search_path()

            assert dialog.search_paths.count(search_path) == 1
            assert dialog.search_list.count() == 1

    def test_add_search_path_cancelled(self, qtbot, mock_project):
        """Test cancelling search path dialog"""
        from pyPhotoAlbum.asset_heal_dialog import AssetHealDialog

        with patch.object(AssetHealDialog, '_scan_missing_assets'):
            dialog = AssetHealDialog(mock_project)
            qtbot.addWidget(dialog)

            with patch.object(QFileDialog, 'getExistingDirectory', return_value=""):
                dialog._add_search_path()

            assert len(dialog.search_paths) == 0

    def test_remove_search_path(self, qtbot, mock_project, tmp_path):
        """Test removing a search path"""
        from pyPhotoAlbum.asset_heal_dialog import AssetHealDialog

        with patch.object(AssetHealDialog, '_scan_missing_assets'):
            dialog = AssetHealDialog(mock_project)
            qtbot.addWidget(dialog)

            search_path = str(tmp_path / "search")
            dialog.search_paths.append(search_path)
            dialog.search_list.addItem(search_path)

            dialog.search_list.setCurrentRow(0)
            dialog._remove_search_path()

            assert len(dialog.search_paths) == 0
            assert dialog.search_list.count() == 0

    def test_remove_search_path_none_selected(self, qtbot, mock_project):
        """Test removing when no path is selected"""
        from pyPhotoAlbum.asset_heal_dialog import AssetHealDialog

        with patch.object(AssetHealDialog, '_scan_missing_assets'):
            dialog = AssetHealDialog(mock_project)
            qtbot.addWidget(dialog)

            dialog.search_paths.append("/some/path")
            dialog.search_list.addItem("/some/path")

            dialog.search_list.setCurrentRow(-1)  # No selection
            dialog._remove_search_path()

            # Should not remove anything
            assert len(dialog.search_paths) == 1

    @patch('pyPhotoAlbum.models.set_asset_resolution_context')
    def test_attempt_healing_no_missing_assets(self, mock_set_context, qtbot, mock_project):
        """Test healing when there are no missing assets"""
        from pyPhotoAlbum.asset_heal_dialog import AssetHealDialog

        mock_project.pages = []

        dialog = AssetHealDialog(mock_project)
        qtbot.addWidget(dialog)

        with patch.object(QMessageBox, 'information') as mock_info:
            dialog._attempt_healing()

            mock_info.assert_called_once()
            args = mock_info.call_args[0]
            assert "Assets found: 0" in args[2]

    @patch('pyPhotoAlbum.models.set_asset_resolution_context')
    def test_attempt_healing_resolve_relative_path(self, mock_set_context, qtbot, mock_project, tmp_path):
        """Test healing by resolving relative path"""
        from pyPhotoAlbum.asset_heal_dialog import AssetHealDialog
        from pyPhotoAlbum.models import ImageData

        # Create the actual image file outside project
        external_img = tmp_path / "external" / "image.jpg"
        external_img.parent.mkdir(exist_ok=True)
        external_img.touch()

        # Element with relative path that resolves to external image
        element = Mock(spec=ImageData)
        rel_path = os.path.relpath(str(external_img), mock_project.folder_path)
        element.image_path = rel_path

        page = Mock()
        page.layout = Mock()
        page.layout.elements = [element]

        mock_project.pages = [page]

        dialog = AssetHealDialog(mock_project)
        qtbot.addWidget(dialog)

        # Mock the import_asset method
        mock_project.asset_manager.import_asset.return_value = "assets/image.jpg"

        with patch.object(QMessageBox, 'information') as mock_info:
            dialog._attempt_healing()

            # Should have imported the asset
            mock_project.asset_manager.import_asset.assert_called()

    @patch('pyPhotoAlbum.models.set_asset_resolution_context')
    def test_attempt_healing_absolute_path_exists(self, mock_set_context, qtbot, mock_project, tmp_path):
        """Test healing absolute path that exists"""
        from pyPhotoAlbum.asset_heal_dialog import AssetHealDialog
        from pyPhotoAlbum.models import ImageData

        # Create image at absolute path
        abs_img = tmp_path / "image.jpg"
        abs_img.touch()

        element = Mock(spec=ImageData)
        element.image_path = str(abs_img)

        page = Mock()
        page.layout = Mock()
        page.layout.elements = [element]

        mock_project.pages = [page]

        dialog = AssetHealDialog(mock_project)
        qtbot.addWidget(dialog)

        mock_project.asset_manager.import_asset.return_value = "assets/image.jpg"

        with patch.object(QMessageBox, 'information') as mock_info:
            dialog._attempt_healing()

            # Should import the asset
            mock_project.asset_manager.import_asset.assert_called_with(str(abs_img))
            # Should update element path
            assert element.image_path == "assets/image.jpg"

    @patch('pyPhotoAlbum.models.set_asset_resolution_context')
    def test_attempt_healing_search_path_by_filename(self, mock_set_context, qtbot, mock_project, tmp_path):
        """Test healing by finding file in search path by filename"""
        from pyPhotoAlbum.asset_heal_dialog import AssetHealDialog
        from pyPhotoAlbum.models import ImageData

        # Create search path with image
        search_dir = tmp_path / "search"
        search_dir.mkdir()
        found_img = search_dir / "photo.jpg"
        found_img.touch()

        element = Mock(spec=ImageData)
        element.image_path = "/missing/path/photo.jpg"

        page = Mock()
        page.layout = Mock()
        page.layout.elements = [element]

        mock_project.pages = [page]

        with patch.object(AssetHealDialog, '_scan_missing_assets'):
            dialog = AssetHealDialog(mock_project)
            qtbot.addWidget(dialog)

            dialog.missing_assets.add("/missing/path/photo.jpg")
            dialog.search_paths.append(str(search_dir))

            mock_project.asset_manager.import_asset.return_value = "assets/photo.jpg"

            with patch.object(QMessageBox, 'information'):
                dialog._attempt_healing()

            mock_project.asset_manager.import_asset.assert_called_with(str(found_img))

    @patch('pyPhotoAlbum.models.set_asset_resolution_context')
    def test_attempt_healing_search_path_by_relative_structure(self, mock_set_context, qtbot, mock_project, tmp_path):
        """Test healing by finding file in search path with same relative structure"""
        from pyPhotoAlbum.asset_heal_dialog import AssetHealDialog
        from pyPhotoAlbum.models import ImageData

        # Create search path with subdirectory structure
        search_dir = tmp_path / "search"
        subdir = search_dir / "photos"
        subdir.mkdir(parents=True)
        found_img = subdir / "image.jpg"
        found_img.touch()

        element = Mock(spec=ImageData)
        element.image_path = "photos/image.jpg"

        page = Mock()
        page.layout = Mock()
        page.layout.elements = [element]

        mock_project.pages = [page]

        with patch.object(AssetHealDialog, '_scan_missing_assets'):
            dialog = AssetHealDialog(mock_project)
            qtbot.addWidget(dialog)

            dialog.missing_assets.add("photos/image.jpg")
            dialog.search_paths.append(str(search_dir))

            mock_project.asset_manager.import_asset.return_value = "assets/image.jpg"

            with patch.object(QMessageBox, 'information'):
                dialog._attempt_healing()

            mock_project.asset_manager.import_asset.assert_called_with(str(found_img))

    @patch('pyPhotoAlbum.models.set_asset_resolution_context')
    def test_attempt_healing_restore_to_assets_folder(self, mock_set_context, qtbot, mock_project, tmp_path):
        """Test healing by restoring file to assets folder"""
        from pyPhotoAlbum.asset_heal_dialog import AssetHealDialog
        from pyPhotoAlbum.models import ImageData

        # Create source image
        source_img = tmp_path / "source" / "image.jpg"
        source_img.parent.mkdir()
        source_img.touch()

        element = Mock(spec=ImageData)
        element.image_path = "assets/image.jpg"  # Already correct path, just missing file

        page = Mock()
        page.layout = Mock()
        page.layout.elements = [element]

        mock_project.pages = [page]

        with patch.object(AssetHealDialog, '_scan_missing_assets'):
            dialog = AssetHealDialog(mock_project)
            qtbot.addWidget(dialog)

            dialog.missing_assets.add("assets/image.jpg")
            dialog.search_paths.append(str(source_img.parent))

            with patch('shutil.copy2') as mock_copy:
                with patch.object(QMessageBox, 'information'):
                    dialog._attempt_healing()

                # Should copy file, not import
                mock_copy.assert_called_once()
                assert not mock_project.asset_manager.import_asset.called

    @patch('pyPhotoAlbum.models.set_asset_resolution_context')
    def test_attempt_healing_not_found(self, mock_set_context, qtbot, mock_project):
        """Test healing when asset cannot be found"""
        from pyPhotoAlbum.asset_heal_dialog import AssetHealDialog
        from pyPhotoAlbum.models import ImageData

        element = Mock(spec=ImageData)
        element.image_path = "/missing/image.jpg"

        page = Mock()
        page.layout = Mock()
        page.layout.elements = [element]

        mock_project.pages = [page]

        dialog = AssetHealDialog(mock_project)
        qtbot.addWidget(dialog)

        with patch.object(QMessageBox, 'information') as mock_info:
            dialog._attempt_healing()

            args = mock_info.call_args[0]
            assert "Still missing: 1" in args[2]
            assert "/missing/image.jpg" in args[2]

    @patch('pyPhotoAlbum.models.set_asset_resolution_context')
    def test_attempt_healing_import_error(self, mock_set_context, qtbot, mock_project, tmp_path):
        """Test healing when import_asset raises an error"""
        from pyPhotoAlbum.asset_heal_dialog import AssetHealDialog
        from pyPhotoAlbum.models import ImageData

        # Create image
        img = tmp_path / "image.jpg"
        img.touch()

        element = Mock(spec=ImageData)
        element.image_path = str(img)

        page = Mock()
        page.layout = Mock()
        page.layout.elements = [element]

        mock_project.pages = [page]

        dialog = AssetHealDialog(mock_project)
        qtbot.addWidget(dialog)

        # Mock import to raise error
        mock_project.asset_manager.import_asset.side_effect = Exception("Import failed")

        with patch.object(QMessageBox, 'information') as mock_info:
            dialog._attempt_healing()

            # Should be in still_missing list
            args = mock_info.call_args[0]
            assert "Still missing: 1" in args[2]

    @patch('pyPhotoAlbum.models.set_asset_resolution_context')
    def test_attempt_healing_many_missing(self, mock_set_context, qtbot, mock_project):
        """Test healing with more than 10 missing assets (tests truncation)"""
        from pyPhotoAlbum.asset_heal_dialog import AssetHealDialog
        from pyPhotoAlbum.models import ImageData

        pages = []
        for i in range(15):
            element = Mock(spec=ImageData)
            element.image_path = f"/missing/image{i}.jpg"

            page = Mock()
            page.layout = Mock()
            page.layout.elements = [element]
            pages.append(page)

        mock_project.pages = pages

        dialog = AssetHealDialog(mock_project)
        qtbot.addWidget(dialog)

        with patch.object(QMessageBox, 'information') as mock_info:
            dialog._attempt_healing()

            args = mock_info.call_args[0]
            # Should show "... and X more"
            assert "and 5 more" in args[2]

    @patch('pyPhotoAlbum.models.set_asset_resolution_context')
    def test_attempt_healing_multiple_elements_same_path(self, mock_set_context, qtbot, mock_project, tmp_path):
        """Test healing when multiple elements reference the same missing path"""
        from pyPhotoAlbum.asset_heal_dialog import AssetHealDialog
        from pyPhotoAlbum.models import ImageData

        # Create image
        img = tmp_path / "image.jpg"
        img.touch()

        # Two elements with same path
        element1 = Mock(spec=ImageData)
        element1.image_path = str(img)
        element2 = Mock(spec=ImageData)
        element2.image_path = str(img)

        page = Mock()
        page.layout = Mock()
        page.layout.elements = [element1, element2]

        mock_project.pages = [page]

        dialog = AssetHealDialog(mock_project)
        qtbot.addWidget(dialog)

        mock_project.asset_manager.import_asset.return_value = "assets/image.jpg"

        with patch.object(QMessageBox, 'information'):
            dialog._attempt_healing()

        # Both elements should be updated
        assert element1.image_path == "assets/image.jpg"
        assert element2.image_path == "assets/image.jpg"

    @patch('pyPhotoAlbum.models.set_asset_resolution_context')
    def test_attempt_healing_rescans_after(self, mock_set_context, qtbot, mock_project):
        """Test that _scan_missing_assets is called after healing"""
        from pyPhotoAlbum.asset_heal_dialog import AssetHealDialog

        mock_project.pages = []

        dialog = AssetHealDialog(mock_project)
        qtbot.addWidget(dialog)

        with patch.object(dialog, '_scan_missing_assets') as mock_scan:
            with patch.object(QMessageBox, 'information'):
                dialog._attempt_healing()

            # Should rescan after healing
            mock_scan.assert_called_once()
