"""
init-mater-project

Copyright (C) 2025 [Lauranne Sarribouette] <lauranne.sarribouette@univ-grenoble-alpes.fr>

SPDX-License-Identifier: LGPL-3.0-or-later
"""

import pytest
from unittest.mock import patch

from src.init_mater_project.src.lib.dimensions import (
    combine_dimensions,
    create_dimensions_mapping,
    resolve_single_mapping_entry,
    create_reference_indexes,
    find_dimension_in_reference,
    build_complete_hierarchy_from_mapping,
    normalize_dimension_name,
    get_parent_value,
)


class TestCombineDimensions:
    """Test combine_dimensions function"""

    def test_standard_match_with_reference(self, sample_reference_dimensions):
        """Test local spain matches reference spain and merges with equivalence"""
        local = [
            {
                "name": "location",
                "value": "spain",
                "equivalence": {},
                "parents_values": {"default": "iberia"},
            }
        ]

        result = combine_dimensions(local, sample_reference_dimensions)

        spain = next(r for r in result if r["value"] == "spain")
        assert spain["equivalence"]["iso_alpha2"] == "ES"
        assert spain["parents_values"]["default"] == "europe"

    def test_no_match_keeps_local(self, sample_reference_dimensions):
        """Test local mars not in reference is kept as-is and reference added"""
        local = [
            {
                "name": "location",
                "value": "mars",
                "equivalence": {},
                "parents_values": {"default": "solar"},
            }
        ]

        result = combine_dimensions(local, sample_reference_dimensions)

        mars = next((r for r in result if r["value"] == "mars"), None)
        assert mars is not None
        assert mars["parents_values"]["default"] == "solar"

    def test_dedup_duplicate_entries(self):
        """Test two madrid entries with different parents merged with hierarchy_1"""
        local = [
            {
                "name": "location",
                "value": "madrid",
                "equivalence": {},
                "parents_values": {"default": "spain"},
            },
            {
                "name": "location",
                "value": "madrid",
                "equivalence": {},
                "parents_values": {"default": "castile"},
            },
        ]

        result = combine_dimensions(local, [])

        assert len(result) == 1
        assert result[0]["parents_values"]["default"] == "spain"
        assert result[0]["parents_values"]["hierarchy_1"] == "castile"

    def test_empty_local_or_reference(self, sample_reference_dimensions):
        """Test empty local or empty reference handled correctly"""
        # Empty local
        result1 = combine_dimensions([], sample_reference_dimensions)
        assert len(result1) > 0

        # Empty ref
        local = [
            {
                "name": "location",
                "value": "madrid",
                "equivalence": {},
                "parents_values": {"default": "spain"},
            }
        ]
        result2 = combine_dimensions(local, [])
        assert len(result2) == 1
        assert result2[0] == local[0]

    def test_multiple_dimension_names_handled_separately(self):
        """Test multiple dimension names (location + object) handled separately"""
        local = [
            {
                "name": "location",
                "value": "madrid",
                "equivalence": {},
                "parents_values": {"default": "spain"},
            },
            {
                "name": "object",
                "value": "car",
                "equivalence": {},
                "parents_values": {"default": "vehicle"},
            },
        ]
        ref = [
            {
                "name": "location",
                "value": "spain",
                "equivalence": {},
                "parents_values": {"default": "europe"},
            }
        ]

        result = combine_dimensions(local, ref)

        locations = [r for r in result if r["name"] == "location"]
        objects = [r for r in result if r["name"] == "object"]
        assert len(locations) == 2
        assert len(objects) == 1


class TestCreateDimensionsMapping:
    """Test create_dimensions_mapping function"""

    def test_mapping_creation_with_matches(
        self, sample_input_dimensions, sample_reference_dimensions
    ):
        """Test input dimensions found in reference create mapping with equivalence and parents"""
        result = create_dimensions_mapping(
            sample_input_dimensions, sample_reference_dimensions
        )

        assert len(result) > 0
        for entry in result:
            assert "name" in entry
            assert "value" in entry
            assert "equivalence" in entry
            assert "parents_values" in entry

    def test_mapping_creation_without_reference(self, sample_input_dimensions):
        """Test empty reference creates mapping with TODOs"""
        result = create_dimensions_mapping(sample_input_dimensions, [])

        assert len(result) > 0
        for entry in result:
            assert "TODO" in entry["equivalence"]
            assert "TODO" in entry["parents_values"]


class TestResolveSingleMappingEntry:
    """Test resolve_single_mapping_entry function"""

    def test_resolve_complete_entry(self, sample_reference_dimensions):
        """Test entry with valid reference and parent resolved without errors"""
        ref_index = create_reference_indexes(sample_reference_dimensions)
        entry = {
            "name": "location",
            "value": "spain",
            "equivalence": {"iso_alpha2": "ES"},
            "parents_values": {"default": "europe"},
        }

        resolved, errors, auto_resolved = resolve_single_mapping_entry(
            entry, ref_index, []
        )

        assert errors == []
        assert not auto_resolved
        assert resolved["parents_values"]["default"] == "europe"

    def test_resolve_auto_parent_from_reference(self, sample_reference_dimensions):
        """Test entry with reference but empty parents auto-resolves parent from reference"""
        ref_index = create_reference_indexes(sample_reference_dimensions)
        entry = {
            "name": "location",
            "value": "spain",
            "equivalence": {"iso_alpha2": "ES"},
            "parents_values": {},
        }

        resolved, errors, auto_resolved = resolve_single_mapping_entry(
            entry, ref_index, []
        )

        assert errors == []
        assert auto_resolved
        assert resolved["parents_values"]["default"] == "europe"

    def test_resolve_errors_with_todos_or_invalid_parent(
        self, sample_reference_dimensions
    ):
        """Test entry with TODOs or invalid parent returns errors"""
        ref_index = create_reference_indexes(sample_reference_dimensions)

        # TODOs
        entry_todo = {
            "name": "location",
            "value": "spain",
            "equivalence": {"TODO": "check"},
            "parents_values": {"default": "europe"},
        }
        _, errors1, _ = resolve_single_mapping_entry(entry_todo, ref_index, [])
        assert len(errors1) == 1
        assert "TODO" in errors1[0]

        # Invalid parent
        entry_invalid = {
            "name": "location",
            "value": "spain",
            "equivalence": {},
            "parents_values": {"default": "invalid_parent"},
        }
        _, errors2, _ = resolve_single_mapping_entry(entry_invalid, ref_index, [])
        assert len(errors2) == 1
        assert "not found" in errors2[0]


class TestReferenceIndexing:
    """Test create_reference_indexes and find_dimension_in_reference"""

    def test_indexing_and_search_operations(self, sample_reference_dimensions):
        """Test indexing direct and equivalence values enables successful search"""
        ref_index = create_reference_indexes(sample_reference_dimensions)

        # Direct match
        result1 = find_dimension_in_reference("location", "spain", ref_index)
        assert result1 is not None
        assert result1["value"] == "spain"

        # Equivalence match
        result2 = find_dimension_in_reference("location", "ES", ref_index)
        assert result2 is not None
        assert result2["value"] == "spain"

    def test_case_insensitive_search_operations(self, sample_reference_dimensions):
        """Test search operations with different cases (SPAIN, spain, SpAin) all find spain"""
        ref_index = create_reference_indexes(sample_reference_dimensions)

        for variant in ["SPAIN", "spain", "SpAin", "ES", "es"]:
            result = find_dimension_in_reference("location", variant, ref_index)
            assert result is not None
            assert result["value"] == "spain"


class TestBuildCompleteHierarchy:
    """Test build_complete_hierarchy_from_mapping function"""

    @patch("src.init_mater_project.src.lib.dimensions.load_reference_dimensions_data")
    @patch("pathlib.Path.exists")
    def test_build_hierarchy_with_missing_parents(
        self, mock_exists, mock_load_ref, complete_mapping
    ):
        """Test mapping with missing parents creates parents automatically"""
        mock_exists.return_value = True
        mock_load_ref.return_value = []

        result = build_complete_hierarchy_from_mapping(complete_mapping)

        assert len(result) >= len(complete_mapping)
        parent_values = [e["value"] for e in result]
        assert "world" in parent_values
        assert "vehicule" in parent_values

    def test_build_hierarchy_fails_with_todos(self, incomplete_mapping):
        """Test mapping with TODOs raises ValueError"""
        with pytest.raises(ValueError, match="Cannot build entry with TODO keys"):
            build_complete_hierarchy_from_mapping(incomplete_mapping)


class TestUtilityFunctions:
    """Test utility functions"""

    def test_normalize_base_and_derived_names(self):
        """Test base and derived dimension names normalized correctly"""
        assert normalize_dimension_name("location") == "location"
        assert normalize_dimension_name("location_production") == "location"
        assert normalize_dimension_name("object_composition") == "object"
        assert normalize_dimension_name("process") == "process"

    def test_get_parent_value_or_none_for_root(self):
        """Test get parent value returns value or None for root"""
        entry_with_parent = {"parents_values": {"default": "world"}}
        assert get_parent_value(entry_with_parent) == "world"

        entry_root = {"parents_values": {}}
        assert get_parent_value(entry_root) is None
