"""Tests for pypack module."""

from __future__ import annotations

import json
from pathlib import Path

import pytest

from sfi.pypack.pypack import (
    AssemblePackageTask,
    GenerateLoaderTask,
    InstallPythonTask,
    PackageWorkflow,
    PackSourceTask,
    ParseProjectTask,
)


@pytest.fixture
def temp_project_dir(tmp_path: Path) -> Path:
    """Create a temporary project directory with pyproject.toml."""
    project_dir = tmp_path / "test_project"
    project_dir.mkdir()

    # Create pyproject.toml - write as plain text to avoid dependency on toml/tomli_w
    pyproject_content = """[project]
name = "test_project"
version = "1.0.0"
description = "Test project"
dependencies = ["requests>=2.0.0"]
"""

    with open(project_dir / "pyproject.toml", "w", encoding="utf-8") as f:
        f.write(pyproject_content)

    return project_dir


class TestParseProjectTask:
    """Tests for ParseProjectTask."""

    @pytest.mark.asyncio
    async def test_parse_project_success(self, temp_project_dir: Path, mocker):
        """Test successful project parsing."""
        # Mock parse_project_data - it's imported inside execute() method
        mock_parse = mocker.patch("sfi.projectparse.projectparse.parse_project_data")
        mock_parse.return_value = {"test_project": {"version": "1.0.0", "dependencies": ["requests>=2.0.0"]}}

        task = ParseProjectTask(directory=temp_project_dir, recursive=False)
        result = await task.execute({})

        assert "projects" in result
        assert "test_project" in result["projects"]
        assert result["projects"]["test_project"]["version"] == "1.0.0"

        # Verify projects.json was created
        assert (temp_project_dir / "projects.json").exists()

        # Clean up
        (temp_project_dir / "projects.json").unlink()


class TestPackSourceTask:
    """Tests for PackSourceTask."""

    @pytest.mark.asyncio
    async def test_pack_source_success(self, temp_project_dir: Path, mocker):
        """Test successful source packing."""
        # Create projects.json - use dict format as expected by pack_project
        projects = {"test_project": {"version": "1.0.0"}}
        with open(temp_project_dir / "projects.json", "w", encoding="utf-8") as f:
            json.dump(projects, f)

        # Mock load_projects and pack_project - they're imported inside execute() method
        mock_load = mocker.patch("sfi.pysourcepack.pysourcepack.load_projects")
        mock_load.return_value = projects
        mock_pack = mocker.patch("sfi.pysourcepack.pysourcepack.pack_project")
        mock_pack.return_value = None

        task = PackSourceTask(directory=temp_project_dir, projects_file=temp_project_dir / "projects.json")
        result = await task.execute({})

        assert "packed_projects" in result
        assert "test_project" in result["packed_projects"]


class TestInstallPythonTask:
    """Tests for InstallPythonTask."""

    @pytest.mark.asyncio
    async def test_install_python_success(self, temp_project_dir: Path, mocker):
        """Test successful Python installation."""
        # Mock install_embed_python - it's imported inside execute() method
        mock_install = mocker.patch("sfi.embedinstall.embedinstall.install_embed_python")
        mock_install.return_value = {"success": True}

        task = InstallPythonTask(version="3.8.10", target_dir=temp_project_dir / "dist" / "runtime")
        result = await task.execute({})

        assert result["version"] == "3.8.10"
        assert result["install_result"]["success"] is True


class TestGenerateLoaderTask:
    """Tests for GenerateLoaderTask."""

    @pytest.mark.asyncio
    async def test_generate_loader_success(self, temp_project_dir: Path, mocker):
        """Test successful loader generation."""
        # Mock pyloadergen functions - they're imported inside execute() method
        mock_project_info_class = mocker.patch("sfi.pyloadergen.pyloadergen.ProjectInfo")
        mock_project_info = mocker.MagicMock()
        mock_project_info_class.from_dict.return_value = mock_project_info

        mocker.patch("sfi.pyloadergen.pyloadergen.find_compiler", return_value="gcc")
        mocker.patch("sfi.pyloadergen.pyloadergen.generate_loader", return_value=True)

        output_dir = temp_project_dir / "dist"
        output_dir.mkdir(parents=True)

        # Create mock inputs with parse_project result
        from sfi.workflowengine.workflowengine import TaskResult

        mock_inputs = {
            "parse_project": TaskResult(
                task_id="parse_project",
                success=True,
                data={
                    "projects": {"test_project": {"version": "1.0.0"}},
                    "projects_file": str(temp_project_dir / "projects.json"),
                },
                execution_time=0.1,
            )
        }

        task = GenerateLoaderTask(
            project_dir=temp_project_dir, project_name="test_project", output_dir=output_dir, is_debug=False
        )
        result = await task.execute(mock_inputs)

        assert "output_exe" in result
        assert "success" in result
        assert result["success"] is True


class TestAssemblePackageTask:
    """Tests for AssemblePackageTask."""

    @pytest.mark.asyncio
    async def test_assemble_package_success(self, temp_project_dir: Path):
        """Test successful package assembly."""
        output_dir = temp_project_dir / "dist"
        output_dir.mkdir(parents=True)

        # Create mock inputs
        from sfi.workflowengine.workflowengine import TaskResult

        mock_inputs = {
            "parse_project": TaskResult(
                task_id="parse_project",
                success=True,
                data={"projects_file": str(temp_project_dir / "projects.json")},
                execution_time=0.1,
            )
        }

        task = AssemblePackageTask(output_dir=output_dir, dependencies=[])
        # Call the internal assemble function
        result = task.process_func(mock_inputs, {})

        assert "output_dir" in result
        assert "metadata" in result


class TestPackageWorkflow:
    """Tests for PackageWorkflow."""

    def test_build_workflow(self, temp_project_dir: Path):
        """Test workflow building."""
        config = {
            "project_name": "test_project",
            "python_version": "3.8.10",
            "loader_type": "console",
            "entry_file": "main.py",
            "generate_loader": True,
            "recursive": False,
            "offline": False,
            "max_concurrent": 4,
        }

        workflow = PackageWorkflow(directory=temp_project_dir, config=config)
        engine = workflow.build_workflow()

        # Workflow now includes: parse, pack, install, pack_libraries, generate_loader, assemble
        assert len(engine.tasks) == 6

    @pytest.mark.asyncio
    async def test_execute_workflow(self, temp_project_dir: Path, mocker):
        """Test workflow execution."""
        config = {
            "project_name": None,
            "python_version": "3.8.10",
            "loader_type": "console",
            "entry_file": "main.py",
            "generate_loader": False,  # Skip loader for simpler test
            "recursive": False,
            "offline": False,
            "max_concurrent": 4,
        }

        # Mock all external dependencies - they're imported inside execute() methods
        mocker.patch("sfi.projectparse.projectparse.parse_project_data", return_value={})
        mocker.patch("sfi.pysourcepack.pysourcepack.load_projects", return_value={})
        mocker.patch("sfi.pysourcepack.pysourcepack.pack_project")
        mocker.patch("sfi.embedinstall.embedinstall.install_embed_python")

        workflow = PackageWorkflow(directory=temp_project_dir, config=config)

        # Note: Full workflow execution would require more complex mocking
        # This test primarily verifies the workflow can be built
        engine = workflow.build_workflow()
        assert engine is not None


class TestTaskDependencies:
    """Tests for task dependencies."""

    def test_parse_task_no_dependencies(self):
        """Test that parse task has no dependencies."""
        task = ParseProjectTask(directory=Path("/tmp"))
        assert len(task.dependencies) == 0

    def test_pack_task_depends_on_parse(self):
        """Test that pack task depends on parse."""
        task = PackSourceTask(
            directory=Path("/tmp"),
            projects_file=Path("/tmp/projects.json"),
        )
        assert "parse_project" in task.dependencies

    def test_install_task_depends_on_parse(self):
        """Test that install task depends on parse."""
        task = InstallPythonTask(version="3.8.10", target_dir=Path("/tmp/dist/runtime"))
        assert "parse_project" in task.dependencies

    def test_assemble_task_dependencies(self):
        """Test that assemble task depends on all previous tasks."""
        task = AssemblePackageTask(
            output_dir=Path("/tmp/dist"), dependencies=["pack_source", "install_python", "generate_loader"]
        )
        expected_deps = {"pack_source", "install_python", "generate_loader"}
        assert set(task.dependencies) == expected_deps


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
