"""Tests for the plugin discovery module."""

from pathlib import Path

import pytest

from mld_sdk._discover import (
    DiscoveredPlugin,
    discover_plugin,
    get_sdk_dependency_spec,
    _parse_entry_point,
    _parse_routes_prefix_from_source,
    _read_full_pyproject,
)


MINIMAL_PYPROJECT = """\
[project]
name = "mld-plugin-test"
version = "1.0.0"
description = "A test plugin"
dependencies = ["mld-sdk>=0.9.0", "fastapi>=0.109.0"]

[project.entry-points."mld.plugins"]
test-analysis = "mld_plugin_test.plugin:TestAnalysisPlugin"
"""


@pytest.fixture
def plugin_project(tmp_path):
    project = tmp_path / "mld-plugin-test"
    project.mkdir()
    (project / "pyproject.toml").write_text(MINIMAL_PYPROJECT)
    # Create source with PLUGIN_ROUTES_PREFIX
    src = project / "src" / "mld_plugin_test"
    src.mkdir(parents=True)
    (src / "plugin.py").write_text(
        'PLUGIN_ROUTES_PREFIX = "/test"\n'
        "\n"
        "class TestAnalysisPlugin:\n"
        "    pass\n"
    )
    return project


class TestReadFullPyproject:

    def test_reads_full_data(self, plugin_project):
        data = _read_full_pyproject(plugin_project)
        assert "project" in data
        assert data["project"]["name"] == "mld-plugin-test"
        # entry-points are nested under project
        entry_points = data["project"]["entry-points"]["mld.plugins"]
        assert "test-analysis" in entry_points

    def test_missing_file(self, tmp_path):
        with pytest.raises(SystemExit):
            _read_full_pyproject(tmp_path)


class TestParseEntryPoint:

    def test_parses_entry_point(self):
        data = {
            "project": {
                "entry-points": {
                    "mld.plugins": {
                        "test-analysis": "mld_plugin_test.plugin:TestAnalysisPlugin"
                    }
                }
            }
        }
        result = _parse_entry_point(data)
        assert result == ("test-analysis", "mld_plugin_test.plugin", "TestAnalysisPlugin")

    def test_no_entry_points(self):
        data = {"project": {"name": "test"}}
        assert _parse_entry_point(data) is None

    def test_no_colon_in_value(self):
        data = {
            "project": {
                "entry-points": {
                    "mld.plugins": {
                        "test": "mld_plugin_test.plugin"
                    }
                }
            }
        }
        assert _parse_entry_point(data) is None


class TestParseRoutesPrefix:

    def test_parses_from_source(self, plugin_project):
        prefix = _parse_routes_prefix_from_source(
            "mld_plugin_test.plugin", plugin_project
        )
        assert prefix == "/test"

    def test_double_quotes(self, tmp_path):
        src = tmp_path / "src" / "mypkg"
        src.mkdir(parents=True)
        (src / "plugin.py").write_text('PLUGIN_ROUTES_PREFIX = "/my-route"\n')
        prefix = _parse_routes_prefix_from_source("mypkg.plugin", tmp_path)
        assert prefix == "/my-route"

    def test_single_quotes(self, tmp_path):
        src = tmp_path / "src" / "mypkg"
        src.mkdir(parents=True)
        (src / "plugin.py").write_text("PLUGIN_ROUTES_PREFIX = '/single'\n")
        prefix = _parse_routes_prefix_from_source("mypkg.plugin", tmp_path)
        assert prefix == "/single"

    def test_missing_source(self, tmp_path):
        prefix = _parse_routes_prefix_from_source("nonexistent.plugin", tmp_path)
        assert prefix is None

    def test_no_prefix_in_source(self, tmp_path):
        src = tmp_path / "src" / "mypkg"
        src.mkdir(parents=True)
        (src / "plugin.py").write_text("class MyPlugin:\n    pass\n")
        prefix = _parse_routes_prefix_from_source("mypkg.plugin", tmp_path)
        assert prefix is None


class TestDiscoverPlugin:

    def test_full_discovery(self, plugin_project):
        result = discover_plugin(plugin_project)
        assert isinstance(result, DiscoveredPlugin)
        assert result.entry_point_name == "test-analysis"
        assert result.module_path == "mld_plugin_test.plugin"
        assert result.class_name == "TestAnalysisPlugin"
        assert result.project_name == "mld-plugin-test"
        assert result.project_version == "1.0.0"
        assert result.project_description == "A test plugin"
        assert result.routes_prefix == "/test"
        assert result.factory_target == "mld_plugin_test.plugin:create_standalone_app"
        assert result.has_frontend is False

    def test_routes_prefix_from_source(self, plugin_project):
        result = discover_plugin(plugin_project)
        # Should use /test from source, not /test-analysis from entry point name
        assert result.routes_prefix == "/test"

    def test_routes_prefix_fallback(self, tmp_path):
        project = tmp_path / "fallback-plugin"
        project.mkdir()
        (project / "pyproject.toml").write_text(
            '[project]\n'
            'name = "fallback-plugin"\n'
            'version = "0.1.0"\n'
            'description = "Fallback test"\n'
            'dependencies = []\n'
            '\n'
            '[project.entry-points."mld.plugins"]\n'
            'my-route = "fallback_plugin.plugin:FallbackPlugin"\n'
        )
        # Create source WITHOUT PLUGIN_ROUTES_PREFIX
        src = project / "src" / "fallback_plugin"
        src.mkdir(parents=True)
        (src / "plugin.py").write_text("class FallbackPlugin:\n    pass\n")
        result = discover_plugin(project)
        assert result.routes_prefix == "/my-route"

    def test_no_entry_point(self, tmp_path):
        (tmp_path / "pyproject.toml").write_text(
            '[project]\nname = "no-ep"\nversion = "1.0"\ndescription = ""\n'
        )
        with pytest.raises(SystemExit):
            discover_plugin(tmp_path)

    def test_no_project_table(self, tmp_path):
        (tmp_path / "pyproject.toml").write_text("[build-system]\n")
        with pytest.raises(SystemExit):
            discover_plugin(tmp_path)

    def test_dynamic_version(self, tmp_path):
        (tmp_path / "pyproject.toml").write_text(
            '[project]\n'
            'name = "dynamic-plugin"\n'
            'dynamic = ["version"]\n'
            'description = "Dynamic version"\n'
            'dependencies = []\n'
            '\n'
            '[project.entry-points."mld.plugins"]\n'
            'dyn = "dyn_plugin.plugin:DynPlugin"\n'
        )
        src = tmp_path / "src" / "dyn_plugin"
        src.mkdir(parents=True)
        (src / "plugin.py").write_text("class DynPlugin:\n    pass\n")
        result = discover_plugin(tmp_path)
        assert result.project_version is None

    def test_has_frontend(self, plugin_project):
        frontend = plugin_project / "frontend"
        frontend.mkdir()
        (frontend / "package.json").write_text('{"name": "test-frontend"}')
        result = discover_plugin(plugin_project)
        assert result.has_frontend is True

    def test_no_frontend(self, plugin_project):
        result = discover_plugin(plugin_project)
        assert result.has_frontend is False

    def test_factory_target(self, plugin_project):
        result = discover_plugin(plugin_project)
        assert result.factory_target == "mld_plugin_test.plugin:create_standalone_app"


class TestGetSdkDependencySpec:

    def test_extracts_spec(self, plugin_project):
        spec = get_sdk_dependency_spec(plugin_project)
        assert spec == ">=0.9.0"

    def test_no_mld_sdk(self, tmp_path):
        (tmp_path / "pyproject.toml").write_text(
            '[project]\nname = "no-sdk"\nversion = "1.0"\n'
            'dependencies = ["fastapi>=0.109.0"]\n'
        )
        assert get_sdk_dependency_spec(tmp_path) is None

    def test_bare_dependency(self, tmp_path):
        (tmp_path / "pyproject.toml").write_text(
            '[project]\nname = "bare"\nversion = "1.0"\n'
            'dependencies = ["mld-sdk"]\n'
        )
        assert get_sdk_dependency_spec(tmp_path) == "*"

    def test_underscore_name(self, tmp_path):
        (tmp_path / "pyproject.toml").write_text(
            '[project]\nname = "underscore"\nversion = "1.0"\n'
            'dependencies = ["mld_sdk>=1.0"]\n'
        )
        assert get_sdk_dependency_spec(tmp_path) == ">=1.0"
