"""Tests for pyprojectparse module."""

from __future__ import annotations

import json
from pathlib import Path

import pytest
from pytest_mock import MockerFixture

from ..components.parser import create_parser, main
from ..models.dependency import Dependency
from ..models.project import Project
from ..models.solution import Solution


@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()

    pyproject_content = """[project]
name = "test_project"
version = "1.0.0"
description = "Test project"
readme = "README.md"
requires-python = ">=3.8"
dependencies = ["requests>=2.0.0"]
optional-dependencies = {dev = ["pytest>=7.0.0"]}
scripts = {cli = "main:app"}
entry-points = {"console_scripts" = {app = "main:main"}}
authors = [{name = "Test Author"}]
license = {text = "MIT"}
keywords = ["test", "project"]
classifiers = ["Programming Language :: Python :: 3"]
urls = {homepage = "https://example.com"}

[build-system]
requires = ["setuptools>=45", "wheel"]
build-backend = "setuptools.build_meta"
"""

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

    return project_dir


@pytest.fixture
def temp_projects_dir(tmp_path: Path) -> Path:
    """Create a temporary directory with multiple projects."""
    base_dir = tmp_path / "projects"
    base_dir.mkdir()

    # Create project1
    project1 = base_dir / "project1"
    project1.mkdir()
    with open(project1 / "pyproject.toml", "w", encoding="utf-8") as f:
        f.write('[project]\nname = "project1"\nversion = "1.0.0"\n')

    # Create project2
    project2 = base_dir / "project2"
    project2.mkdir()
    with open(project2 / "pyproject.toml", "w", encoding="utf-8") as f:
        f.write('[project]\nname = "project2"\nversion = "2.0.0"\n')

    return base_dir


@pytest.fixture
def temp_project_invalid_json(tmp_path: Path) -> Path:
    """Create a temporary project.json file with invalid data."""
    projects_json = tmp_path / "projects.json"
    with open(projects_json, "w", encoding="utf-8") as f:
        f.write("invalid json")
    return tmp_path


@pytest.fixture
def temp_project_empty_toml(tmp_path: Path) -> Path:
    """Create a temporary pyproject.toml file with invalid data."""
    project_toml = tmp_path / "pyproject.toml"
    with open(project_toml, "w", encoding="utf-8") as f:
        f.write("")
    return tmp_path


@pytest.fixture
def temp_project_invalid_toml(tmp_path: Path) -> Path:
    """Create a temporary pyproject.toml file with invalid data."""
    project_toml = tmp_path / "pyproject.toml"
    with open(project_toml, "w", encoding="utf-8") as f:
        f.write("invalid toml")
    return tmp_path


class TestProjectClass:
    """Tests for Project dataclass methods and properties."""

    def test_project_initialization(self):
        """Test Project dataclass initialization with all attributes."""
        data = Project(
            name="test",
            version="1.0.0",
            description="test desc",
            readme="README.md",
            requires_python=">=3.8",
            dependencies=["requests"],
            optional_dependencies={},
            scripts={},
            entry_points={},
            authors=[],
            license="MIT",
            keywords=[],
            classifiers=[],
            urls={},
            build_backend="setuptools.build_meta",
            requires=[],
            toml_path=Path("pyproject.toml"),
        )
        assert data.name == "test"
        assert data.version == "1.0.0"

    def test_project_repr_method(self):
        """Test Project __repr__ method output format."""
        data = Project(
            name="test",
            version="1.0.0",
            description="",
            readme="",
            requires_python=">=3.8",
            dependencies=["requests"],
            optional_dependencies={},
            scripts={},
            entry_points={},
            authors=[],
            license="",
            keywords=[],
            classifiers=[],
            urls={},
            build_backend="",
            requires=[],
            toml_path=Path("pyproject.toml"),
        )
        repr_str = repr(data)
        assert "test" in repr_str
        assert "1.0.0" in repr_str
        assert "requests" in repr_str

    def test_project_raw_data_property(self):
        """Test Project raw_data property for JSON serialization."""
        data = Project(
            name="test-project",
            version="1.0.0",
            description="desc",
            readme="README.md",
            requires_python=">=3.8",
            dependencies=["requests"],
            optional_dependencies={"dev": ["pytest"]},
            scripts={"cli": "main:app"},
            entry_points={"console_scripts": {"app": "main:main"}},
            authors=[{"name": "Test"}],
            license="MIT",
            keywords=["test"],
            classifiers=["Class::Test"],
            urls={"homepage": "https://example.com"},
            build_backend="setuptools.build_meta",
            requires=["setuptools"],
            toml_path=Path("pyproject.toml"),
        )
        result = data.raw_data
        assert result["name"] == "test-project"
        assert result["version"] == "1.0.0"
        assert result["dependencies"] == ["requests"]
        assert result["optional_dependencies"]["dev"] == ["pytest"]

    def test_project_properties(self):
        """Test Project calculated properties."""
        data = Project(
            name="test-project",
            version="1.0.0",
            description="",
            readme="",
            requires_python=">=3.8",
            dependencies=[
                "requests>=2.0.0",
                "PySide2; python_version<'3.10'",
                "numpy[extra]",
            ],
            optional_dependencies={},
            scripts={},
            entry_points={},
            authors=[],
            license="",
            keywords=["gui", "desktop"],
            classifiers=[],
            urls={},
            build_backend="",
            requires=[],
            toml_path=Path("pyproject.toml"),
        )
        assert data.normalized_name == "test_project"
        assert data.dep_names == {"requests", "PySide2", "numpy"}
        assert data.has_qt is True
        assert data.qt_deps == {"PySide2"}
        assert data.qt_libname_raw == "PySide2"  # 原始名称
        assert data.qt_libname == "PySide2"  # 映射后的正确目录名
        assert data.is_gui is True
        assert data.loader_type == "gui"

    def test_project_no_qt_properties(self):
        """Test Project properties when no Qt is present."""
        data = Project(
            name="test",
            version="1.0.0",
            description="",
            readme="",
            requires_python=">=3.8",
            dependencies=["requests"],
            optional_dependencies={},
            scripts={},
            entry_points={},
            authors=[],
            license="",
            keywords=[],
            classifiers=[],
            urls={},
            build_backend="",
            requires=[],
            toml_path=Path("pyproject.toml"),
        )
        assert data.has_qt is False
        assert data.qt_libname is None
        assert data.qt_libname_raw is None
        assert data.is_gui is False
        assert data.loader_type == "console"

    def test_project_lowercase_qt_dependency_mapping(self):
        """Test Qt library name mapping from lowercase dependencies to proper directory names."""
        # 测试小写的pyside2依赖应该映射到PySide2目录
        data1 = Project(
            name="test1",
            version="1.0.0",
            description="",
            readme="",
            requires_python=">=3.8",
            dependencies=["pyside2>=5.15.0"],  # 小写依赖名
            optional_dependencies={},
            scripts={},
            entry_points={},
            authors=[],
            license="",
            keywords=["gui"],
            classifiers=[],
            urls={},
            build_backend="",
            requires=[],
            toml_path=Path("pyproject.toml"),
        )
        assert data1.has_qt is True
        assert data1.qt_libname_raw == "pyside2"  # 原始小写名称
        assert data1.qt_libname == "PySide2"  # 映射后的正确目录名
        assert data1.is_gui is True

        # 测试小写的pyqt5依赖应该映射到PyQt5目录
        data2 = Project(
            name="test2",
            version="1.0.0",
            description="",
            readme="",
            requires_python=">=3.8",
            dependencies=["pyqt5>=5.15.0"],  # 小写依赖名
            optional_dependencies={},
            scripts={},
            entry_points={},
            authors=[],
            license="",
            keywords=["gui"],
            classifiers=[],
            urls={},
            build_backend="",
            requires=[],
            toml_path=Path("pyproject.toml"),
        )
        assert data2.has_qt is True
        assert data2.qt_libname_raw == "pyqt5"  # 原始小写名称
        assert data2.qt_libname == "PyQt5"  # 映射后的正确目录名
        assert data2.is_gui is True

    def test_from_dict(self):
        """Test Project from_dict class method."""
        input_dict = {
            "name": "test",
            "version": "1.0.0",
            "description": "desc",
            "readme": "README.md",
            "requires_python": ">=3.8",
            "dependencies": ["requests"],
            "optional_dependencies": {"dev": ["pytest"]},
            "scripts": {"cli": "main:app"},
            "entry_points": {},
            "authors": [{"name": "Test"}],
            "license": "MIT",
            "keywords": ["test"],
            "classifiers": ["Class::Test"],
            "urls": {"homepage": "https://example.com"},
            "build_backend": "setuptools.build_meta",
            "requires": ["setuptools"],
        }
        data = Project._from_dict(input_dict)
        assert data.name == "test"
        assert data.version == "1.0.0"
        assert data.dependencies == ["requests"]
        assert isinstance(data, Project)

    def test_from_dict_with_defaults(self):
        """Test Project from_dict with missing fields."""
        input_dict = {"name": "test", "version": "1.0.0"}
        data = Project._from_dict(input_dict)
        assert data.name == "test"
        assert data.version == "1.0.0"
        assert data.dependencies == []
        assert data.optional_dependencies == {}
        assert data.scripts == {}
        assert data.qt_deps == set()
        assert data.is_gui is False
        assert data.loader_type == "console"


class TestSolutionFromJsonData:
    """Tests for Solution.from_json_data method."""

    def test_from_json_data_valid_projects(self):
        """Test creating Solution from JSON data with valid projects."""
        data = {
            "project1": {"project": {"name": "project1", "version": "1.0.0"}},
            "project2": {"project": {"name": "project2", "version": "2.0.0"}},
        }
        result = Solution.from_json_data(Path.cwd(), data).projects
        assert len(result) == 2
        assert "project1" in result
        assert "project2" in result
        assert isinstance(result["project1"], Project)
        assert result["project1"].name == "project1"

    def test_from_json_data_empty(self):
        """Test creating Solution from empty JSON data."""
        result = Solution.from_json_data(Path.cwd(), {}).projects
        assert result == {}

    def test_from_json_data_no_project_section(self):
        """Test creating Solution from JSON data without project section."""
        data = {"project1": {"build-system": {"requires": ["setuptools"]}}}
        result = Solution.from_json_data(Path.cwd(), data).projects
        assert "project1" in result
        assert result["project1"] == Project._from_empty_dict()

    def test_from_json_data_exception(self):
        """Test from_json_data with invalid structure that raises error."""
        # The method catches Exception, so we just verify it doesn't crash
        result = Solution.from_json_data(Path.cwd(), ["not", "a", "dict"])
        assert isinstance(result, Solution)
        assert len(result.projects) == 0

    def test_from_json_file_not_exist(self):
        """Test from_json_file with non-existent file."""
        result = Solution.from_json_file(Path("non_existent.json"))
        assert result.projects == {}

    def test_from_toml_files_not_a_file(self, tmp_path):
        """Test from_toml_files with a path that is not a file."""
        dir_path = tmp_path / "not_a_file"
        dir_path.mkdir()
        result = Solution.from_toml_files(tmp_path, [dir_path])
        assert result.projects == {}

    def test_from_json_file_invalid_json(self, tmp_path):
        """Test from_json_file with invalid JSON content."""
        json_file = tmp_path / "projects.json"
        with open(json_file, "w", encoding="utf-8") as f:
            f.write("invalid json")
        result = Solution.from_json_file(json_file)
        assert result.projects == {}

    def test_from_toml_file_no_project_section(self, tmp_path):
        """Test from_toml_file with no project section."""
        toml_file = tmp_path / "pyproject.toml"
        with open(toml_file, "w", encoding="utf-8") as f:
            f.write("[build-system]\nrequires = ['setuptools']")
        result = Project.from_toml_file(toml_file)
        assert result == Project._from_empty_dict()

    def test_write_json_unknown_exception(self, mocker, temp_project_dir):
        """Test _write_project_json unknown exception handling."""
        mocker.patch("json.dump", side_effect=RuntimeError("Unexpected error"))
        project_data = {"test": Project._from_dict({"name": "test", "version": "1.0.0"})}
        sol = Solution.from_json_data(temp_project_dir, project_data)
        # Should not raise exception
        sol._write_project_json()
        # Verify the method completed
        assert True


class TestWriteProjectJson:
    """Tests for Solution._write_project_json method."""

    def test_write_json(self, temp_project_dir: Path, tmp_path: Path):
        """Test writing project data to projects.json cache file."""
        project_data = {
            "test_project": Project._from_dict(
                {
                    "name": "test_project",
                    "version": "1.0.0",
                    "description": "test",
                    "readme": "README.md",
                    "requires_python": ">=3.8",
                    "dependencies": ["requests"],
                    "optional_dependencies": {},
                    "scripts": {},
                    "entry_points": {},
                    "authors": [],
                    "license": "",
                    "keywords": [],
                    "classifiers": [],
                    "urls": {},
                    "build_backend": "",
                    "requires": [],
                }
            ),
            "non_dict_data": "some string",
        }

        config = Solution.from_json_data(temp_project_dir, project_data)
        assert config.json_file.exists()

        with open(config.json_file, encoding="utf-8") as f:
            loaded_data = json.load(f)

        assert "test_project" in loaded_data
        assert loaded_data["test_project"]["name"] == "test_project"
        assert loaded_data["non_dict_data"] == "some string"

    def test_write_json_exception(self, mocker, temp_project_dir):
        """Test _write_project_json exception handling."""
        mocker.patch("pathlib.Path.open", side_effect=OSError("Disk full"))
        project_data = {"test": Project._from_dict({"name": "test", "version": "1.0.0"})}
        # Should not raise exception
        Solution(temp_project_dir, project_data)
        assert True


class TestSolutionFromDirectory:
    """Tests for Solution.from_directory method."""

    def test_from_directory_success(self, temp_project_dir: Path):
        """Test successful parsing of pyproject.toml files from directory."""
        config = Solution.from_directory(temp_project_dir)

        assert "test_project" in config.projects
        assert isinstance(config.projects["test_project"], Project)
        assert config.projects["test_project"].name == "test_project"
        assert config.projects["test_project"].version == "1.0.0"

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

    def test_from_directory_with_update_flag(self, temp_project_dir: Path):
        """Test parsing always re-parses (no update flag anymore)."""
        # First parse
        result1 = Solution.from_directory(temp_project_dir).projects
        assert "test_project" in result1

        # Second parse - should be the same result since we always re-parse
        result2 = Solution.from_directory(temp_project_dir).projects
        assert "test_project" in result2
        assert result1["test_project"].name == result2["test_project"].name

    def test_from_directory_load_from_cache(self, temp_project_dir: Path):
        """Test that projects.json is always overwritten (no cache loading)."""
        # First create projects.json with cached data
        projects_json = temp_project_dir / "projects.json"
        test_data = {
            "cached_project": {
                "name": "cached_project",
                "version": "0.9.0",
                "description": "cached",
                "readme": "",
                "requires_python": ">=3.8",
                "dependencies": [],
                "optional_dependencies": {},
                "scripts": {},
                "entry_points": {},
                "authors": [],
                "license": "",
                "keywords": [],
                "classifiers": [],
                "urls": {},
                "build_backend": "",
                "requires": [],
            }
        }
        with open(projects_json, "w", encoding="utf-8") as f:
            json.dump(test_data, f)

        # Parse directory - should ignore cache and re-parse pyproject.toml
        result = Solution.from_directory(temp_project_dir).projects

        # Should contain the actual project from pyproject.toml, not the cached one
        assert "test_project" in result  # From pyproject.toml
        assert "cached_project" not in result  # Cache should be ignored

    def test_from_directory_recursive_scan(self, temp_projects_dir: Path):
        """Test parsing multiple projects recursively from subdirectories."""
        result = Solution.from_directory(temp_projects_dir).projects
        assert "project1" in result
        assert "project2" in result
        assert result["project1"].name == "project1"
        assert result["project2"].name == "project2"


class TestCreateParser:
    """Tests for create_parser function."""

    def test_create_parser(self):
        """Test argument parser creation."""
        parser = create_parser()
        assert "pyproject.toml" in parser.description

    def test_parse_args_default(self):
        """Test parsing arguments with defaults."""
        parser = create_parser()
        args = parser.parse_args([])
        assert args.directory == str(Path.cwd())
        assert args.debug is False

    def test_parse_args_with_options(self):
        """Test parsing arguments with options."""
        parser = create_parser()
        args = parser.parse_args(["--debug", "/test/path"])
        assert args.directory == "/test/path"
        assert args.debug is True

    def test_parse_args_short_options(self):
        """Test parsing arguments with short options."""
        parser = create_parser()
        args = parser.parse_args(["-d", "/test/path"])
        assert args.directory == "/test/path"
        assert args.debug is True

    def test_main(self, mocker):
        """Test main function."""
        mock_from_directory = mocker.patch("pytola.dev.pypack.models.Solution.from_directory")
        mocker.patch("sys.argv", ["pyprojectparse", "--debug", "some_dir"])

        main()

        mock_from_directory.assert_called_once()
        args, _kwargs = mock_from_directory.call_args
        assert args[0] == Path("some_dir")


class TestFullWorkflow:
    """End-to-end tests for complete pyprojectparse workflow."""

    def test_full_workflow(self, temp_project_dir: Path):
        """Test complete workflow: parse pyproject.toml -> create Solution -> save to JSON."""
        # Parse project
        result = Solution.from_directory(temp_project_dir).projects

        # Verify result
        assert "test_project" in result
        assert isinstance(result["test_project"], Project)

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

        # Verify JSON content
        with open(projects_json, encoding="utf-8") as f:
            json_data = json.load(f)

        assert "test_project" in json_data
        assert json_data["test_project"]["name"] == "test_project"

        # Clean up
        projects_json.unlink()


class TestDirectoryNotExist:
    """Tests for handling non-existent directories."""

    def test_directory_not_exist(self):
        """Test Solution.from_directory with non-existent directory."""
        invalid_directory = Path("/non/existent/directory")
        # This should not raise an exception
        result = Solution.from_directory(invalid_directory)
        # Should return empty solution
        assert len(result.projects) == 0
        assert result.root_dir == invalid_directory


class TestInvalidProjectJson:
    """Tests for handling invalid projects.json files."""

    def test_invalid_project_json(self, temp_project_invalid_json: Path, caplog: pytest.LogCaptureFixture):
        """Test that invalid projects.json is ignored and directory is re-scanned."""
        # This should not raise an error since we always re-parse pyproject.toml files
        # and ignore any existing projects.json
        solution = Solution.from_directory(temp_project_invalid_json)

        # Should have 0 projects since there are no valid pyproject.toml files
        assert len(solution.projects) == 0

        # Should still create a new projects.json file
        assert (temp_project_invalid_json / "projects.json").exists()

    def test_unkown_error_parsing_project_json(
        self,
        mocker: MockerFixture,
        temp_project_invalid_json: Path,
    ):
        """Test parsing with unknown error parsing projects.json."""
        mocker.patch("pathlib.Path.open", side_effect=Exception("Test error"))
        # Should not raise exception
        Solution.from_directory(temp_project_invalid_json)
        assert True


class TestInvalidProjectToml:
    """Tests for handling invalid pyproject.toml files."""

    def test_empty_pyproject_toml(
        self,
        temp_project_empty_toml: Path,
    ):
        """Test Solution.from_directory with empty pyproject.toml file."""
        result = Solution.from_directory(temp_project_empty_toml)
        assert len(result.projects) == 0

    def test_invalid_pyproject_toml(
        self,
        temp_project_invalid_toml: Path,
    ):
        """Test Solution.from_directory with invalid pyproject.toml file."""
        result = Solution.from_directory(temp_project_invalid_toml)
        assert len(result.projects) == 0

    def test_from_toml_file_not_exist(self):
        """Test from_toml_file with non-existent file."""
        result = Project.from_toml_file(Path("non_existent.toml"))
        assert result == Project._from_empty_dict()

    def test_from_toml_file_no_name(self, tmp_path):
        """Test from_toml_file with project section but no name."""
        toml_file = tmp_path / "pyproject.toml"
        with open(toml_file, "w", encoding="utf-8") as f:
            f.write("[project]\nversion = '1.0.0'")
        result = Project.from_toml_file(toml_file)
        assert result == Project._from_empty_dict()


class TestDependency:
    """Tests for Dependency dataclass methods and properties."""

    def test_dependency_initialization(self):
        """Test Dependency dataclass initialization with all attributes."""
        dep = Dependency(name="requests", version=">=2.0.0", extras={"security"}, requires=set())
        assert dep.name == "requests"
        assert dep.version == ">=2.0.0"
        assert "security" in dep.extras
        assert isinstance(dep.requires, set)

    def test_dependency_from_str_simple(self):
        """Test creating Dependency from simple string."""
        dep = Dependency.from_str("requests")
        assert dep.name == "requests"
        assert dep.version is None
        assert dep.extras == set()

    def test_dependency_from_str_with_version(self):
        """Test creating Dependency from string with version specifier."""
        dep = Dependency.from_str("requests>=2.0.0")
        assert dep.name == "requests"
        assert dep.version == ">=2.0.0"
        assert dep.extras == set()

    def test_dependency_from_str_with_extras(self):
        """Test creating Dependency from string with extras."""
        dep = Dependency.from_str("requests[security]")
        assert dep.name == "requests"
        assert dep.extras == {"security"}

    def test_dependency_from_str_with_extras_and_version(self):
        """Test creating Dependency from string with extras and version."""
        dep = Dependency.from_str("requests[security]>=2.0.0")
        assert dep.name == "requests"
        assert dep.extras == {"security"}
        assert dep.version == ">=2.0.0"

    def test_dependency_from_str_multiple_extras(self):
        """Test creating Dependency from string with multiple extras."""
        dep = Dependency.from_str("requests[security,socks]>=2.0.0")
        assert dep.name == "requests"
        assert dep.extras == {"security", "socks"}
        assert dep.version == ">=2.0.0"

    def test_dependency_normalization(self):
        """Test that dependency names are normalized (hyphens to underscores)."""
        dep = Dependency.from_str("Requests-OAuthLib")
        assert dep.name == "requests_oauthlib"
        assert dep.normalized_name == "requests_oauthlib"

    def test_dependency_str_representation(self):
        """Test string representation of Dependency."""
        dep = Dependency(name="requests", version=">=2.0.0")
        assert str(dep) == "requests>=2.0.0"

        dep_with_extras = Dependency(name="requests", version=">=2.0.0", extras={"security"})
        result = str(dep_with_extras)
        assert "requests" in result
        assert "security" in result
        assert ">=2.0.0" in result

    def test_dependency_hash_and_equality(self):
        """Test that Dependency objects can be hashed and compared for equality."""
        dep1 = Dependency(name="requests", version=">=2.0.0", extras={"security"})
        dep2 = Dependency(name="requests", version=">=2.0.0", extras={"security"})
        dep3 = Dependency(name="requests", version=">=2.0.0", extras={"other"})

        assert dep1 == dep2
        assert hash(dep1) == hash(dep2)
        assert dep1 != dep3

    def test_converted_dependencies_property(self):
        """Test Project.converted_dependencies property."""
        project = Project._from_dict(
            {
                "name": "test",
                "version": "1.0.0",
                "description": "",
                "readme": "",
                "requires_python": "",
                "dependencies": ["requests>=2.0.0", "numpy[extra]"],
                "optional_dependencies": {},
                "scripts": {},
                "entry_points": {},
                "authors": [],
                "license": "",
                "keywords": [],
                "classifiers": [],
                "urls": {},
                "build_backend": "",
                "requires": [],
            }
        )

        converted_deps = project.converted_dependencies
        assert len(converted_deps) == 2

        # Check that the dependencies were properly converted
        dep_names = {dep.name for dep in converted_deps}
        assert "requests" in dep_names
        assert "numpy" in dep_names

    def test_min_python_version_property(self):
        """Test Project.min_python_version property with various formats."""
        # Test different version formats
        test_cases = [
            ({"requires_python": ">=3.8"}, "3.8"),
            ({"requires_python": ">3.7"}, "3.7"),
            ({"requires_python": "~=3.9"}, "3.9"),
            ({"requires_python": "==3.8"}, "3.8"),
            ({"requires_python": "=3.8"}, "3.8"),
            ({"requires_python": "==3.8.*"}, "3.8"),
            ({"requires_python": ""}, None),
            ({"requires_python": "invalid"}, None),
        ]

        for input_data, expected in test_cases:
            project_data = {
                "name": "test",
                "version": "1.0.0",
                "description": "",
                "readme": "",
                "requires_python": input_data["requires_python"],
                "dependencies": [],
                "optional_dependencies": {},
                "scripts": {},
                "entry_points": {},
                "authors": [],
                "license": "",
                "keywords": [],
                "classifiers": [],
                "urls": {},
                "build_backend": "",
                "requires": [],
            }

            project = Project._from_dict(project_data)
            assert project.min_python_version == expected

    def test_dependencies_property(self):
        """Test Solution.dependencies property."""
        project1 = Project._from_dict(
            {
                "name": "project1",
                "version": "1.0.0",
                "description": "",
                "readme": "",
                "requires_python": "",
                "dependencies": ["requests", "numpy"],
                "optional_dependencies": {},
                "scripts": {},
                "entry_points": {},
                "authors": [],
                "license": "",
                "keywords": [],
                "classifiers": [],
                "urls": {},
                "build_backend": "",
                "requires": [],
            }
        )

        project2 = Project._from_dict(
            {
                "name": "project2",
                "version": "1.0.0",
                "description": "",
                "readme": "",
                "requires_python": "",
                "dependencies": ["requests", "pandas"],
                "optional_dependencies": {},
                "scripts": {},
                "entry_points": {},
                "authors": [],
                "license": "",
                "keywords": [],
                "classifiers": [],
                "urls": {},
                "build_backend": "",
                "requires": [],
            }
        )

        solution = Solution(root_dir=Path("/tmp"), projects={"proj1": project1, "proj2": project2})
        all_deps = solution.dependencies

        # Should contain all unique dependencies from both projects
        expected_deps = {"requests", "numpy", "pandas"}
        assert all_deps == expected_deps
