"""Fixture-driven tests for value type rules (ND0202, ND0302)."""

from pathlib import Path

import pytest

from fixtures import workflows_dir
from valn8n_lib.rules import get_rules
from valn8n_lib.validator import validate_workflow

FIXTURE_DIR = workflows_dir() / "test_wrongvaltype"


def _discover_cases() -> list[str]:
    """Discover test cases from .lst companion files."""
    return sorted(p.stem for p in FIXTURE_DIR.glob("*.lst"))


@pytest.mark.parametrize("case", _discover_cases())
def test_val_type_rule(case: str) -> None:
    lst_path = FIXTURE_DIR / f"{case}.lst"
    json_path = FIXTURE_DIR / f"{case}.json"
    assert json_path.exists(), f"Missing fixture: {json_path}"

    expected = sorted(line.strip() for line in lst_path.read_text().splitlines() if line.strip())

    diagnostics = validate_workflow(json_path, rules=get_rules())
    actual = sorted(d.code.code for d in diagnostics)

    assert actual == expected, f"{case}: expected {expected}, got {actual}"


class TestND0202EdgeCases:
    """Edge case tests for ND0202 — top-level node key value types."""

    def test_valid_types_no_diagnostics(self) -> None:
        """A valid node should produce no ND0202 diagnostics."""
        from valn8n_lib.validator import validate_workflow

        json_path = FIXTURE_DIR / "wrongpos.json"
        # Use wrongpos but only check that a *valid* node would pass
        # Create a minimal valid workflow inline
        import json
        import tempfile

        workflow = {
            "nodes": [
                {
                    "id": "abc-123",
                    "name": "Test Node",
                    "type": "n8n-nodes-base.noOp",
                    "typeVersion": 1,
                    "position": [100, 200],
                    "parameters": {},
                }
            ]
        }
        with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
            json.dump(workflow, f)
            tmp_path = Path(f.name)
        try:
            diagnostics = validate_workflow(tmp_path, rules=get_rules(select={"ND0202"}))
            assert not diagnostics
        finally:
            tmp_path.unlink()

    def test_wrong_position_element_type(self) -> None:
        """position: ['a', 300] should flag ND0202."""
        json_path = FIXTURE_DIR / "wrongpos.json"
        diagnostics = validate_workflow(json_path, rules=get_rules(select={"ND0202"}))
        assert len(diagnostics) == 1
        assert diagnostics[0].code.code == "ND0202"
        assert "position" in diagnostics[0].message

    def test_wrong_typeversion_type(self) -> None:
        """typeVersion: '1' (string) should flag ND0202."""
        json_path = FIXTURE_DIR / "wrong_typeversion.json"
        diagnostics = validate_workflow(json_path, rules=get_rules(select={"ND0202"}))
        assert len(diagnostics) == 1
        assert diagnostics[0].code.code == "ND0202"
        assert "typeVersion" in diagnostics[0].message


class TestND0302EdgeCases:
    """Edge case tests for ND0302 — parameter value types."""

    def test_expression_skipped(self) -> None:
        """Expression values (={{...}}) should not trigger ND0302."""
        import json
        import tempfile

        workflow = {
            "nodes": [
                {
                    "id": "abc-789",
                    "name": "Brandfetch Node",
                    "type": "n8n-nodes-base.Brandfetch",
                    "typeVersion": 1,
                    "position": [100, 200],
                    "parameters": {"domain": "={{ $json.domain }}"},
                }
            ]
        }
        with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
            json.dump(workflow, f)
            tmp_path = Path(f.name)
        try:
            diagnostics = validate_workflow(tmp_path, rules=get_rules(select={"ND0302"}))
            assert not diagnostics
        finally:
            tmp_path.unlink()

    def test_expression_skipped(self) -> None:
        """Expressions (={{ ... }}) should not trigger ND0302."""
        from unittest.mock import patch

        from valn8n_lib.rules.nd_0302 import check_param_value_types

        catalog = {
            "n8n-nodes-base.testNode": {
                "properties": [
                    {
                        "name": "auth",
                        "type": "options",
                        "options": [
                            {"name": "OAuth2", "value": "oAuth2"},
                        ],
                    }
                ]
            }
        }
        workflow = {
            "nodes": [
                {
                    "id": "n1",
                    "name": "Test",
                    "type": "n8n-nodes-base.testNode",
                    "typeVersion": 1,
                    "position": [0, 0],
                    "parameters": {"auth": "={{ $credentials.oAuth2 }}"},
                }
            ]
        }
        with patch("valn8n_lib.rules.nd_0302.NODE_CATALOG", catalog):
            diags = check_param_value_types(workflow, Path("/fake.json"))
        assert not diags

    def test_embedded_template_expression_skipped(self) -> None:
        """Embedded template expressions should not trigger ND0302."""
        from unittest.mock import patch

        from valn8n_lib.rules.nd_0302 import check_param_value_types

        catalog = {
            "n8n-nodes-base.testNode": {
                "properties": [
                    {
                        "name": "domain",
                        "type": "string",
                    }
                ]
            }
        }
        workflow = {
            "nodes": [
                {
                    "id": "n1",
                    "name": "Test",
                    "type": "n8n-nodes-base.testNode",
                    "typeVersion": 1,
                    "position": [0, 0],
                    "parameters": {"domain": "some text {{ $json.field }}"},
                }
            ]
        }
        with patch("valn8n_lib.rules.nd_0302.NODE_CATALOG", catalog):
            diags = check_param_value_types(workflow, Path("/fake.json"))
        assert not diags

    def test_unknown_node_type_skipped(self) -> None:
        """Nodes not in catalog should not trigger ND0302."""
        import json
        import tempfile

        workflow = {
            "nodes": [
                {
                    "id": "abc-000",
                    "name": "Custom Node",
                    "type": "n8n-nodes-custom.myNode",
                    "typeVersion": 1,
                    "position": [100, 200],
                    "parameters": {"someParam": 12345},
                }
            ]
        }
        with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
            json.dump(workflow, f)
            tmp_path = Path(f.name)
        try:
            diagnostics = validate_workflow(tmp_path, rules=get_rules(select={"ND0302"}))
            assert not diagnostics
        finally:
            tmp_path.unlink()

    def test_wrong_param_type_flagged(self) -> None:
        """A number where a string is expected should flag ND0302."""
        json_path = FIXTURE_DIR / "wrong_param_type.json"
        diagnostics = validate_workflow(json_path, rules=get_rules(select={"ND0302"}))
        assert len(diagnostics) == 1
        assert diagnostics[0].code.code == "ND0302"
        assert "domain" in diagnostics[0].message

    def test_options_int_with_load_options_method(self) -> None:
        """An integer value for an options param with loadOptionsMethod should not flag."""
        from unittest.mock import patch

        from valn8n_lib.rules.nd_0302 import check_param_value_types

        catalog = {
            "n8n-nodes-base.testNode": {
                "properties": [
                    {
                        "name": "accountId",
                        "type": "options",
                        "typeOptions": {"loadOptionsMethod": "getAccounts"},
                    }
                ]
            }
        }
        workflow = {
            "nodes": [
                {
                    "id": "n1",
                    "name": "Test",
                    "type": "n8n-nodes-base.testNode",
                    "typeVersion": 1,
                    "position": [0, 0],
                    "parameters": {"accountId": 12345},
                }
            ]
        }
        with patch("valn8n_lib.rules.nd_0302.NODE_CATALOG", catalog):
            diags = check_param_value_types(workflow, Path("/fake.json"))
        assert not diags

    def test_options_int_matching_static_option(self) -> None:
        """An integer value matching a static option should not flag."""
        from unittest.mock import patch

        from valn8n_lib.rules.nd_0302 import check_param_value_types

        catalog = {
            "n8n-nodes-base.testNode": {
                "properties": [
                    {
                        "name": "priority",
                        "type": "options",
                        "options": [
                            {"name": "Low", "value": 1},
                            {"name": "High", "value": 2},
                        ],
                    }
                ]
            }
        }
        workflow = {
            "nodes": [
                {
                    "id": "n1",
                    "name": "Test",
                    "type": "n8n-nodes-base.testNode",
                    "typeVersion": 1,
                    "position": [0, 0],
                    "parameters": {"priority": 2},
                }
            ]
        }
        with patch("valn8n_lib.rules.nd_0302.NODE_CATALOG", catalog):
            diags = check_param_value_types(workflow, Path("/fake.json"))
        assert not diags

    def test_options_dict_value_flags(self) -> None:
        """A dict value for an options param should flag ND0302."""
        from unittest.mock import patch

        from valn8n_lib.rules.nd_0302 import check_param_value_types

        catalog = {
            "n8n-nodes-base.testNode": {
                "properties": [
                    {
                        "name": "mode",
                        "type": "options",
                        "typeOptions": {"loadOptionsMethod": "getModes"},
                    }
                ]
            }
        }
        workflow = {
            "nodes": [
                {
                    "id": "n1",
                    "name": "Test",
                    "type": "n8n-nodes-base.testNode",
                    "typeVersion": 1,
                    "position": [0, 0],
                    "parameters": {"mode": {"nested": "bad"}},
                }
            ]
        }
        with patch("valn8n_lib.rules.nd_0302.NODE_CATALOG", catalog):
            diags = check_param_value_types(workflow, Path("/fake.json"))
        assert len(diags) == 1
        assert diags[0].code.code == "ND0302"
        assert "scalar" in diags[0].message

    def test_merged_options_prevents_false_positive(self) -> None:
        """Duplicate options params with different displayOptions should be merged."""
        from unittest.mock import patch

        from valn8n_lib.rules.nd_0302 import check_param_value_types

        catalog = {
            "n8n-nodes-base.testNode": {
                "properties": [
                    {
                        "name": "operation",
                        "type": "options",
                        "displayOptions": {"show": {"resource": ["task"]}},
                        "options": [
                            {"name": "Move", "value": "move"},
                            {"name": "Get", "value": "get"},
                        ],
                    },
                    {
                        "name": "operation",
                        "type": "options",
                        "displayOptions": {"show": {"resource": ["project"]}},
                        "options": [
                            {"name": "Create", "value": "create"},
                            {"name": "Delete", "value": "delete"},
                        ],
                    },
                ]
            }
        }
        workflow = {
            "nodes": [
                {
                    "id": "n1",
                    "name": "Test",
                    "type": "n8n-nodes-base.testNode",
                    "typeVersion": 1,
                    "position": [0, 0],
                    "parameters": {"resource": "task", "operation": "move"},
                }
            ]
        }
        with patch("valn8n_lib.rules.nd_0302.NODE_CATALOG", catalog):
            diags = check_param_value_types(workflow, Path("/fake.json"))
        assert not diags

    def test_merged_options_still_flags_invalid(self) -> None:
        """A truly invalid value should still be flagged after merging."""
        from unittest.mock import patch

        from valn8n_lib.rules.nd_0302 import check_param_value_types

        catalog = {
            "n8n-nodes-base.testNode": {
                "properties": [
                    {
                        "name": "operation",
                        "type": "options",
                        "displayOptions": {"show": {"resource": ["task"]}},
                        "options": [
                            {"name": "Move", "value": "move"},
                            {"name": "Get", "value": "get"},
                        ],
                    },
                    {
                        "name": "operation",
                        "type": "options",
                        "displayOptions": {"show": {"resource": ["project"]}},
                        "options": [
                            {"name": "Create", "value": "create"},
                            {"name": "Delete", "value": "delete"},
                        ],
                    },
                ]
            }
        }
        workflow = {
            "nodes": [
                {
                    "id": "n1",
                    "name": "Test",
                    "type": "n8n-nodes-base.testNode",
                    "typeVersion": 1,
                    "position": [0, 0],
                    "parameters": {"resource": "task", "operation": "nonexistent"},
                }
            ]
        }
        with patch("valn8n_lib.rules.nd_0302.NODE_CATALOG", catalog):
            diags = check_param_value_types(workflow, Path("/fake.json"))
        assert len(diags) == 1
        assert diags[0].code.code == "ND0302"

    def test_dynamic_type_options_propagated_on_merge(self) -> None:
        """When one variant has loadOptionsMethod, merged property accepts any scalar."""
        from unittest.mock import patch

        from valn8n_lib.rules.nd_0302 import check_param_value_types

        catalog = {
            "n8n-nodes-base.testNode": {
                "properties": [
                    {
                        "name": "project",
                        "type": "options",
                        "displayOptions": {"show": {"resource": ["task"]}},
                        "options": [
                            {"name": "Alpha", "value": "alpha"},
                        ],
                    },
                    {
                        "name": "project",
                        "type": "options",
                        "displayOptions": {"show": {"resource": ["section"]}},
                        "typeOptions": {"loadOptionsMethod": "getProjects"},
                    },
                ]
            }
        }
        workflow = {
            "nodes": [
                {
                    "id": "n1",
                    "name": "Test",
                    "type": "n8n-nodes-base.testNode",
                    "typeVersion": 1,
                    "position": [0, 0],
                    "parameters": {"resource": "section", "project": 99999},
                }
            ]
        }
        with patch("valn8n_lib.rules.nd_0302.NODE_CATALOG", catalog):
            diags = check_param_value_types(workflow, Path("/fake.json"))
        assert not diags

    def test_multiple_values_string_list_accepted(self) -> None:
        """A list of strings for a string param with multipleValues should not flag."""
        from unittest.mock import patch

        from valn8n_lib.rules.nd_0302 import check_param_value_types

        catalog = {
            "n8n-nodes-base.testNode": {
                "properties": [
                    {
                        "name": "tags",
                        "type": "string",
                        "typeOptions": {"multipleValues": True},
                    }
                ]
            }
        }
        workflow = {
            "nodes": [
                {
                    "id": "n1",
                    "name": "Test",
                    "type": "n8n-nodes-base.testNode",
                    "typeVersion": 1,
                    "position": [0, 0],
                    "parameters": {"tags": ["n8n", "automation"]},
                }
            ]
        }
        with patch("valn8n_lib.rules.nd_0302.NODE_CATALOG", catalog):
            diags = check_param_value_types(workflow, Path("/fake.json"))
        assert not diags

    def test_multiple_values_number_list_accepted(self) -> None:
        """A list of numbers for a number param with multipleValues should not flag."""
        from unittest.mock import patch

        from valn8n_lib.rules.nd_0302 import check_param_value_types

        catalog = {
            "n8n-nodes-base.testNode": {
                "properties": [
                    {
                        "name": "ids",
                        "type": "number",
                        "typeOptions": {"multipleValues": True},
                    }
                ]
            }
        }
        workflow = {
            "nodes": [
                {
                    "id": "n1",
                    "name": "Test",
                    "type": "n8n-nodes-base.testNode",
                    "typeVersion": 1,
                    "position": [0, 0],
                    "parameters": {"ids": [1, 2, 3]},
                }
            ]
        }
        with patch("valn8n_lib.rules.nd_0302.NODE_CATALOG", catalog):
            diags = check_param_value_types(workflow, Path("/fake.json"))
        assert not diags

    def test_multiple_values_collection_list_accepted(self) -> None:
        """A list of objects for a collection param with multipleValues should not flag."""
        from unittest.mock import patch

        from valn8n_lib.rules.nd_0302 import check_param_value_types

        catalog = {
            "n8n-nodes-base.testNode": {
                "properties": [
                    {
                        "name": "attachments",
                        "type": "collection",
                        "typeOptions": {"multipleValues": True},
                        "options": [{"name": "color", "type": "string"}],
                    }
                ]
            }
        }
        workflow = {
            "nodes": [
                {
                    "id": "n1",
                    "name": "Test",
                    "type": "n8n-nodes-base.testNode",
                    "typeVersion": 1,
                    "position": [0, 0],
                    "parameters": {"attachments": [{"color": "red"}, {"color": "blue"}]},
                }
            ]
        }
        with patch("valn8n_lib.rules.nd_0302.NODE_CATALOG", catalog):
            diags = check_param_value_types(workflow, Path("/fake.json"))
        assert not diags

    def test_multiple_values_empty_list_accepted(self) -> None:
        """An empty list for a param with multipleValues should not flag."""
        from unittest.mock import patch

        from valn8n_lib.rules.nd_0302 import check_param_value_types

        catalog = {
            "n8n-nodes-base.testNode": {
                "properties": [
                    {
                        "name": "labels",
                        "type": "collection",
                        "typeOptions": {"multipleValues": True},
                    }
                ]
            }
        }
        workflow = {
            "nodes": [
                {
                    "id": "n1",
                    "name": "Test",
                    "type": "n8n-nodes-base.testNode",
                    "typeVersion": 1,
                    "position": [0, 0],
                    "parameters": {"labels": []},
                }
            ]
        }
        with patch("valn8n_lib.rules.nd_0302.NODE_CATALOG", catalog):
            diags = check_param_value_types(workflow, Path("/fake.json"))
        assert not diags

    def test_multiple_values_scalar_still_flags(self) -> None:
        """A non-list, non-matching scalar for a multipleValues param should still flag."""
        from unittest.mock import patch

        from valn8n_lib.rules.nd_0302 import check_param_value_types

        catalog = {
            "n8n-nodes-base.testNode": {
                "properties": [
                    {
                        "name": "tags",
                        "type": "string",
                        "typeOptions": {"multipleValues": True},
                    }
                ]
            }
        }
        workflow = {
            "nodes": [
                {
                    "id": "n1",
                    "name": "Test",
                    "type": "n8n-nodes-base.testNode",
                    "typeVersion": 1,
                    "position": [0, 0],
                    "parameters": {"tags": 12345},
                }
            ]
        }
        with patch("valn8n_lib.rules.nd_0302.NODE_CATALOG", catalog):
            diags = check_param_value_types(workflow, Path("/fake.json"))
        assert len(diags) == 1
        assert diags[0].code.code == "ND0302"

    def test_no_multiple_values_list_still_flags(self) -> None:
        """A list for a string param WITHOUT multipleValues should still flag."""
        from unittest.mock import patch

        from valn8n_lib.rules.nd_0302 import check_param_value_types

        catalog = {
            "n8n-nodes-base.testNode": {
                "properties": [
                    {
                        "name": "domain",
                        "type": "string",
                    }
                ]
            }
        }
        workflow = {
            "nodes": [
                {
                    "id": "n1",
                    "name": "Test",
                    "type": "n8n-nodes-base.testNode",
                    "typeVersion": 1,
                    "position": [0, 0],
                    "parameters": {"domain": ["a", "b"]},
                }
            ]
        }
        with patch("valn8n_lib.rules.nd_0302.NODE_CATALOG", catalog):
            diags = check_param_value_types(workflow, Path("/fake.json"))
        assert len(diags) == 1
        assert diags[0].code.code == "ND0302"

    def test_resource_locator_string_accepted(self) -> None:
        """A plain string value for a resourceLocator param should not flag ND0302."""
        from unittest.mock import patch

        from valn8n_lib.rules.nd_0302 import check_param_value_types

        catalog = {
            "n8n-nodes-base.testNode": {
                "properties": [
                    {
                        "name": "owner",
                        "type": "resourceLocator",
                    }
                ]
            }
        }
        workflow = {
            "nodes": [
                {
                    "id": "n1",
                    "name": "Test",
                    "type": "n8n-nodes-base.testNode",
                    "typeVersion": 1,
                    "position": [0, 0],
                    "parameters": {"owner": "nodemationqa"},
                }
            ]
        }
        with patch("valn8n_lib.rules.nd_0302.NODE_CATALOG", catalog):
            diags = check_param_value_types(workflow, Path("/fake.json"))
        assert not diags

    def test_resource_locator_dict_accepted(self) -> None:
        """An object value for a resourceLocator param should not flag ND0302."""
        from unittest.mock import patch

        from valn8n_lib.rules.nd_0302 import check_param_value_types

        catalog = {
            "n8n-nodes-base.testNode": {
                "properties": [
                    {
                        "name": "owner",
                        "type": "resourceLocator",
                    }
                ]
            }
        }
        workflow = {
            "nodes": [
                {
                    "id": "n1",
                    "name": "Test",
                    "type": "n8n-nodes-base.testNode",
                    "typeVersion": 1,
                    "position": [0, 0],
                    "parameters": {"owner": {"mode": "list", "value": "foo"}},
                }
            ]
        }
        with patch("valn8n_lib.rules.nd_0302.NODE_CATALOG", catalog):
            diags = check_param_value_types(workflow, Path("/fake.json"))
        assert not diags

    def test_resource_locator_list_rejected(self) -> None:
        """A list value for a resourceLocator param should flag ND0302."""
        from unittest.mock import patch

        from valn8n_lib.rules.nd_0302 import check_param_value_types

        catalog = {
            "n8n-nodes-base.testNode": {
                "properties": [
                    {
                        "name": "owner",
                        "type": "resourceLocator",
                    }
                ]
            }
        }
        workflow = {
            "nodes": [
                {
                    "id": "n1",
                    "name": "Test",
                    "type": "n8n-nodes-base.testNode",
                    "typeVersion": 1,
                    "position": [0, 0],
                    "parameters": {"owner": ["bad", "value"]},
                }
            ]
        }
        with patch("valn8n_lib.rules.nd_0302.NODE_CATALOG", catalog):
            diags = check_param_value_types(workflow, Path("/fake.json"))
        assert len(diags) == 1
        assert diags[0].code.code == "ND0302"
        assert "object or scalar" in diags[0].message
