# Copyright (c) 2026 Luigi Paone <ppc.luigi.paone@gmail.com>
# SPDX-License-Identifier: BSD-3-Clause

"""Tests for pydmodelhelper module."""

from typing import List, Optional

from pydantic import BaseModel

from pycivil.EXAUtils.pydmodelhelper import find_all_submodels, find_submodel


class SimpleModel(BaseModel):
    x: int
    y: str


class NestedModel(BaseModel):
    name: str
    inner: SimpleModel


class DeepModel(BaseModel):
    id: int
    nested: NestedModel


class TestFindAllSubmodels:
    def test_find_in_list(self):
        """Find models nested in a list."""
        data = {"items": [{"x": 1, "y": "a"}, {"x": 2, "y": "b"}]}
        matches = find_all_submodels(data, SimpleModel)
        assert len(matches) == 2
        assert matches[0].x == 1
        assert matches[1].x == 2

    def test_find_nested_in_dict(self):
        """Find models nested inside other dicts."""
        data = {"outer": {"x": 1, "y": "a", "deeper": {"x": 2, "y": "b"}}}
        matches = find_all_submodels(data, SimpleModel)
        assert len(matches) == 2

    def test_find_with_nested_pydantic_model(self):
        """Find models that contain other Pydantic models."""
        data = {
            "wrapper": {
                "name": "parent",
                "inner": {"x": 10, "y": "nested"}
            }
        }
        matches = find_all_submodels(data, NestedModel)
        assert len(matches) == 1
        assert matches[0].name == "parent"
        assert matches[0].inner.x == 10

    def test_find_deeply_nested_models(self):
        """Find models at multiple nesting levels."""
        data = {
            "level1": {
                "id": 1,
                "nested": {
                    "name": "middle",
                    "inner": {"x": 100, "y": "deep"}
                }
            }
        }
        # Find DeepModel
        deep_matches = find_all_submodels(data, DeepModel)
        assert len(deep_matches) == 1
        assert deep_matches[0].id == 1

        # Find NestedModel (inside DeepModel)
        nested_matches = find_all_submodels(data, NestedModel)
        assert len(nested_matches) == 1
        assert nested_matches[0].name == "middle"

        # Find SimpleModel (deepest level)
        simple_matches = find_all_submodels(data, SimpleModel)
        assert len(simple_matches) == 1
        assert simple_matches[0].x == 100

    def test_find_multiple_at_different_levels(self):
        """Find multiple models at different nesting levels."""
        data = {
            "top": {"x": 1, "y": "top"},
            "container": {
                "middle": {"x": 2, "y": "middle"},
                "items": [
                    {"x": 3, "y": "item1"},
                    {"x": 4, "y": "item2"}
                ]
            }
        }
        matches = find_all_submodels(data, SimpleModel)
        assert len(matches) == 4

    def test_no_match(self):
        """Return empty list when no matches found."""
        data = {"foo": "bar", "baz": 123}
        matches = find_all_submodels(data, SimpleModel)
        assert len(matches) == 0

    def test_empty_data(self):
        """Handle empty dict."""
        matches = find_all_submodels({}, SimpleModel)
        assert len(matches) == 0

    def test_list_at_root(self):
        """Handle list at root level."""
        data = [{"x": 1, "y": "a"}, {"x": 2, "y": "b"}, {"other": "data"}]
        matches = find_all_submodels(data, SimpleModel)
        assert len(matches) == 2

    def test_complex_deep_branches(self):
        """Find models in complex structure with many deep branches."""
        data = {
            "project": {
                "metadata": {
                    "info": {"x": 1, "y": "meta"},
                    "tags": ["a", "b", "c"]
                },
                "sections": [
                    {
                        "id": "sec1",
                        "items": [
                            {"x": 2, "y": "sec1-item1"},
                            {"x": 3, "y": "sec1-item2"},
                            {
                                "nested": {
                                    "deep": {
                                        "x": 4,
                                        "y": "sec1-deep"
                                    }
                                }
                            }
                        ]
                    },
                    {
                        "id": "sec2",
                        "config": {
                            "settings": {
                                "params": {
                                    "x": 5,
                                    "y": "sec2-params"
                                }
                            }
                        },
                        "items": [
                            {"x": 6, "y": "sec2-item1"}
                        ]
                    }
                ],
                "footer": {
                    "contact": {
                        "branch1": {
                            "leaf": {"x": 7, "y": "footer-b1"}
                        },
                        "branch2": {
                            "subbranch": {
                                "leaf": {"x": 8, "y": "footer-b2"}
                            }
                        }
                    }
                }
            },
            "sidebar": {
                "widgets": [
                    {"type": "text", "data": {"x": 9, "y": "widget1"}},
                    {"type": "chart", "data": {"other": "stuff"}},
                    {
                        "type": "complex",
                        "data": {
                            "level1": {
                                "level2": {
                                    "level3": {
                                        "level4": {"x": 10, "y": "deep-widget"}
                                    }
                                }
                            }
                        }
                    }
                ]
            }
        }
        matches = find_all_submodels(data, SimpleModel)
        assert len(matches) == 10

        # Verify all values were found
        x_values = sorted([m.x for m in matches])
        assert x_values == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

    def test_mixed_valid_invalid_branches(self):
        """Handle branches with partial matches and invalid data."""
        data = {
            "valid1": {"x": 1, "y": "ok"},
            "invalid1": {"x": "not_int", "y": "fail"},
            "branch": {
                "valid2": {"x": 2, "y": "ok"},
                "partial": {"x": 3},  # missing y
                "nested": {
                    "valid3": {"x": 4, "y": "ok"},
                    "invalid2": {"y": "missing_x"}
                }
            },
            "list_branch": [
                {"x": 5, "y": "ok"},
                {"invalid": True},
                [
                    {"x": 6, "y": "nested-list"},
                    {"bad": "data"}
                ]
            ]
        }
        matches = find_all_submodels(data, SimpleModel)
        assert len(matches) == 5
        x_values = sorted([m.x for m in matches])
        assert x_values == [1, 2, 4, 5, 6]


class TestFindSubmodel:
    def test_find_first_in_list(self):
        """Find first model in a list."""
        data = {"items": [{"x": 1, "y": "first"}, {"x": 2, "y": "second"}]}
        match = find_submodel(data, SimpleModel)
        assert match is not None
        assert match.y == "first"

    def test_find_nested(self):
        """Find model nested in dict."""
        data = {"wrapper": {"deep": {"x": 42, "y": "found"}}}
        match = find_submodel(data, SimpleModel)
        assert match is not None
        assert match.x == 42

    def test_find_with_nested_pydantic_model(self):
        """Find model containing another Pydantic model."""
        data = {
            "wrapper": {
                "name": "test",
                "inner": {"x": 5, "y": "inner"}
            }
        }
        match = find_submodel(data, NestedModel)
        assert match is not None
        assert match.name == "test"
        assert match.inner.x == 5

    def test_no_match_returns_none(self):
        """Return None when no match found."""
        data = {"foo": "bar"}
        match = find_submodel(data, SimpleModel)
        assert match is None

    def test_empty_data_returns_none(self):
        """Return None for empty data."""
        match = find_submodel({}, SimpleModel)
        assert match is None
