import pytest

from blends.models import Language, SyntaxGraph
from blends.stack.policies.base import ExportPolicy
from blends.stack.policies.default import DefaultExportPolicy
from blends.stack.policies.go import GoExportPolicy
from blends.stack.policies.java import JavaExportPolicy
from blends.stack.policies.javascript import JavaScriptExportPolicy
from blends.stack.policies.kotlin import KotlinExportPolicy
from blends.stack.policies.python import PythonExportPolicy
from blends.stack.policies.registry import get_export_policy
from blends.stack.scope_types import ScopeType


@pytest.mark.blends_test_group("stack_unittesting")
@pytest.mark.parametrize(  # type: ignore[misc,attr-defined]
    ("language", "expected_type"),
    [
        (Language.PYTHON, PythonExportPolicy),
        (Language.JAVA, JavaExportPolicy),
        (Language.GO, GoExportPolicy),
        (Language.JAVASCRIPT, JavaScriptExportPolicy),
        (Language.TYPESCRIPT, JavaScriptExportPolicy),
        (Language.KOTLIN, KotlinExportPolicy),
        (Language.SCALA, KotlinExportPolicy),
        (Language.RUBY, DefaultExportPolicy),
    ],
)
def test_registry_returns_correct_policy(  # type: ignore[misc]
    language: Language, expected_type: type[ExportPolicy]
) -> None:
    assert isinstance(get_export_policy(language), expected_type)


@pytest.mark.blends_test_group("stack_unittesting")
@pytest.mark.parametrize(  # type: ignore[misc,attr-defined]
    "policy",
    [
        PythonExportPolicy(),
        JavaExportPolicy(),
        GoExportPolicy(),
        JavaScriptExportPolicy(),
        DefaultExportPolicy(),
    ],
)
def test_file_scope_always_exported(policy: ExportPolicy) -> None:  # type: ignore[misc]
    graph = SyntaxGraph()
    graph.add_node("1")
    assert policy.is_scope_exported(graph, "1", ScopeType.FILE.value) is True


@pytest.mark.blends_test_group("stack_unittesting")
@pytest.mark.parametrize(  # type: ignore[misc,attr-defined]
    ("policy", "scope_type"),
    [
        (PythonExportPolicy(), ScopeType.FUNCTION.value),
        (PythonExportPolicy(), ScopeType.CLASS.value),
        (JavaScriptExportPolicy(), ScopeType.FUNCTION.value),
        (DefaultExportPolicy(), ScopeType.CLASS.value),
    ],
)
def test_non_file_scope_not_exported(  # type: ignore[misc]
    policy: ExportPolicy, scope_type: str
) -> None:
    graph = SyntaxGraph()
    graph.add_node("1")
    assert policy.is_scope_exported(graph, "1", scope_type) is False


@pytest.mark.blends_test_group("stack_unittesting")
def test_java_policy_public_class_scope_exported() -> None:
    policy = JavaExportPolicy()
    graph = SyntaxGraph()
    graph.add_node("1", access_modifiers="public")
    assert policy.is_scope_exported(graph, "1", ScopeType.CLASS.value) is True


@pytest.mark.blends_test_group("stack_unittesting")
@pytest.mark.parametrize(  # type: ignore[misc,attr-defined]
    "modifiers", ["private", None]
)
def test_java_policy_non_public_class_scope_not_exported(  # type: ignore[misc]
    modifiers: str | None,
) -> None:
    policy = JavaExportPolicy()
    graph = SyntaxGraph()
    if modifiers:
        graph.add_node("1", access_modifiers=modifiers)
    else:
        graph.add_node("1")
    assert policy.is_scope_exported(graph, "1", ScopeType.CLASS.value) is False


@pytest.mark.blends_test_group("stack_unittesting")
def test_kotlin_policy_class_without_modifier_exported() -> None:
    policy = KotlinExportPolicy()
    graph = SyntaxGraph()
    graph.add_node("1")
    assert policy.is_scope_exported(graph, "1", ScopeType.CLASS.value) is True


@pytest.mark.blends_test_group("stack_unittesting")
def test_kotlin_policy_private_class_not_exported() -> None:
    policy = KotlinExportPolicy()
    graph = SyntaxGraph()
    graph.add_node("1", access_modifiers="private")
    assert policy.is_scope_exported(graph, "1", ScopeType.CLASS.value) is False


@pytest.mark.blends_test_group("stack_unittesting")
@pytest.mark.parametrize(  # type: ignore[misc,attr-defined]
    "policy",
    [PythonExportPolicy(), JavaScriptExportPolicy(), DefaultExportPolicy()],
)
def test_scope_based_top_level_definition_exported(  # type: ignore[misc]
    policy: ExportPolicy,
) -> None:
    graph = SyntaxGraph()
    graph.add_node("1")
    assert policy.is_definition_exported(graph, "1", None) is True


@pytest.mark.blends_test_group("stack_unittesting")
@pytest.mark.parametrize(  # type: ignore[misc,attr-defined]
    "policy",
    [PythonExportPolicy(), JavaScriptExportPolicy(), DefaultExportPolicy()],
)
def test_scope_based_file_level_definition_exported(  # type: ignore[misc]
    policy: ExportPolicy,
) -> None:
    graph = SyntaxGraph()
    graph.add_node("1")
    graph.add_node("2", stack_graph_scope_type=ScopeType.FILE.value)
    assert policy.is_definition_exported(graph, "1", "2") is True


@pytest.mark.blends_test_group("stack_unittesting")
@pytest.mark.parametrize(  # type: ignore[misc,attr-defined]
    "policy",
    [PythonExportPolicy(), JavaScriptExportPolicy(), DefaultExportPolicy()],
)
def test_scope_based_function_level_definition_not_exported(  # type: ignore[misc]
    policy: ExportPolicy,
) -> None:
    graph = SyntaxGraph()
    graph.add_node("1")
    graph.add_node("2", stack_graph_scope_type=ScopeType.FUNCTION.value)
    assert policy.is_definition_exported(graph, "1", "2") is False


@pytest.mark.blends_test_group("stack_unittesting")
def test_java_policy_public_definition_in_exported_scope() -> None:
    policy = JavaExportPolicy()
    graph = SyntaxGraph()
    graph.add_node("1", access_modifiers="public")
    graph.add_node("2", stack_graph_is_exported=True)
    assert policy.is_definition_exported(graph, "1", "2") is True


@pytest.mark.blends_test_group("stack_unittesting")
def test_java_policy_private_definition_not_exported() -> None:
    policy = JavaExportPolicy()
    graph = SyntaxGraph()
    graph.add_node("1", access_modifiers="private")
    graph.add_node("2", stack_graph_is_exported=True)
    assert policy.is_definition_exported(graph, "1", "2") is False


@pytest.mark.blends_test_group("stack_unittesting")
def test_java_policy_public_definition_in_non_exported_scope() -> None:
    policy = JavaExportPolicy()
    graph = SyntaxGraph()
    graph.add_node("1", access_modifiers="public")
    graph.add_node("2", stack_graph_is_exported=False)
    assert policy.is_definition_exported(graph, "1", "2") is False


@pytest.mark.blends_test_group("stack_unittesting")
def test_kotlin_policy_definition_without_modifier_exported() -> None:
    policy = KotlinExportPolicy()
    graph = SyntaxGraph()
    graph.add_node("1")
    graph.add_node("2", stack_graph_is_exported=True)
    assert policy.is_definition_exported(graph, "1", "2") is True


@pytest.mark.blends_test_group("stack_unittesting")
@pytest.mark.parametrize(  # type: ignore[misc,attr-defined]
    ("node_attrs", "expected"),
    [
        ({"stack_graph_symbol": "ExportedFunc"}, True),
        ({"stack_graph_symbol": "privateFunc"}, False),
        ({"name": "ExportedVar"}, True),
        ({"name": "privateVar"}, False),
    ],
)
def test_go_policy_top_level_capitalization(  # type: ignore[misc]
    node_attrs: dict[str, str],
    expected: bool,  # noqa: FBT001
) -> None:
    policy = GoExportPolicy()
    graph = SyntaxGraph()
    graph.add_node("1", **node_attrs)
    assert policy.is_definition_exported(graph, "1", None) is expected


@pytest.mark.blends_test_group("stack_unittesting")
@pytest.mark.parametrize(  # type: ignore[misc,attr-defined]
    ("scope_type", "expected"),
    [
        (ScopeType.FUNCTION.value, False),
        (ScopeType.FILE.value, True),
    ],
)
def test_go_policy_capitalized_in_scope(  # type: ignore[misc]
    scope_type: str,
    expected: bool,  # noqa: FBT001
) -> None:
    policy = GoExportPolicy()
    graph = SyntaxGraph()
    graph.add_node("1", stack_graph_symbol="ExportedFunc")
    graph.add_node("2", stack_graph_scope_type=scope_type)
    assert policy.is_definition_exported(graph, "1", "2") is expected
