"""Tests for the schema generator module."""

from __future__ import annotations

from ilum.core.config_schemas import ConfigField, ConfigSchema
from ilum.core.schema_generator import (
    _assign_tab,
    _get_nested,
    _infer_field_type,
    _is_sensitive,
    _path_to_label,
    _walk_values,
    generate_module_schema,
)


class TestWalkValues:
    def test_flat_dict(self) -> None:
        data = {"a": 1, "b": "hello"}
        result = list(_walk_values(data))
        assert ("a", 1) in result
        assert ("b", "hello") in result

    def test_nested_dict(self) -> None:
        data = {"image": {"tag": "6.7.0", "repository": "ilum/core"}}
        result = list(_walk_values(data))
        assert ("image.tag", "6.7.0") in result
        assert ("image.repository", "ilum/core") in result

    def test_skips_lists(self) -> None:
        data = {"ports": [8080, 9090], "name": "test"}
        result = list(_walk_values(data))
        assert len(result) == 1
        assert ("name", "test") in result

    def test_deeply_nested(self) -> None:
        data = {"resources": {"requests": {"memory": "2Gi", "cpu": "1"}}}
        result = list(_walk_values(data))
        assert ("resources.requests.memory", "2Gi") in result
        assert ("resources.requests.cpu", "1") in result

    def test_empty_dict(self) -> None:
        assert list(_walk_values({})) == []

    def test_bool_values(self) -> None:
        data = {"enabled": True, "debug": False}
        result = list(_walk_values(data))
        assert ("enabled", True) in result
        assert ("debug", False) in result

    def test_none_values(self) -> None:
        data = {"optional": None}
        result = list(_walk_values(data))
        assert ("optional", None) in result


class TestInferFieldType:
    def test_bool_true(self) -> None:
        assert _infer_field_type(True) == "boolean"

    def test_bool_false(self) -> None:
        assert _infer_field_type(False) == "boolean"

    def test_int(self) -> None:
        assert _infer_field_type(42) == "number"

    def test_float(self) -> None:
        assert _infer_field_type(3.14) == "number"

    def test_string(self) -> None:
        assert _infer_field_type("hello") == "text"

    def test_none(self) -> None:
        assert _infer_field_type(None) == "text"


class TestAssignTab:
    def test_image_tag(self) -> None:
        assert _assign_tab("image.tag") == "general"

    def test_image_repository(self) -> None:
        assert _assign_tab("image.repository") == "general"

    def test_replica_count(self) -> None:
        assert _assign_tab("replicaCount") == "general"

    def test_service_account(self) -> None:
        assert _assign_tab("serviceAccount.create") == "general"

    def test_enabled(self) -> None:
        assert _assign_tab("enabled") == "general"

    def test_resources_requests(self) -> None:
        assert _assign_tab("resources.requests.memory") == "resources"

    def test_resources_limits(self) -> None:
        assert _assign_tab("resources.limits.cpu") == "resources"

    def test_persistence(self) -> None:
        assert _assign_tab("persistence.size") == "storage"

    def test_persistence_enabled(self) -> None:
        assert _assign_tab("persistence.enabled") == "storage"

    def test_storage_class(self) -> None:
        assert _assign_tab("storage.className") == "storage"

    def test_pvc(self) -> None:
        assert _assign_tab("pvc.size") == "storage"

    def test_advanced_fallback(self) -> None:
        assert _assign_tab("someCustomSetting") == "advanced"

    def test_advanced_nested(self) -> None:
        assert _assign_tab("spark.conf.maxExecutors") == "advanced"


class TestPathToLabel:
    def test_known_path(self) -> None:
        assert _path_to_label("image.tag") == "Image Tag"

    def test_resources_memory(self) -> None:
        assert _path_to_label("resources.requests.memory") == "Memory Request"

    def test_replica_count(self) -> None:
        assert _path_to_label("replicaCount") == "Replica Count"

    def test_persistence_size(self) -> None:
        assert _path_to_label("persistence.size") == "Storage Size"

    def test_unknown_camel_case(self) -> None:
        label = _path_to_label("someCustomSetting")
        # Should split camelCase and title-case
        assert "Custom" in label
        assert "Setting" in label

    def test_simple_word(self) -> None:
        label = _path_to_label("debug")
        assert label == "Debug"


class TestIsSensitive:
    def test_password(self) -> None:
        assert _is_sensitive("grafana.adminPassword") is True

    def test_secret(self) -> None:
        assert _is_sensitive("auth.clientSecret") is True

    def test_token(self) -> None:
        assert _is_sensitive("api.accessToken") is True

    def test_key(self) -> None:
        assert _is_sensitive("auth.apiKey") is True

    def test_credential(self) -> None:
        assert _is_sensitive("db.credential") is True

    def test_normal_path(self) -> None:
        assert _is_sensitive("image.tag") is False

    def test_resources(self) -> None:
        assert _is_sensitive("resources.requests.memory") is False


class TestGetNested:
    def test_simple(self) -> None:
        assert _get_nested({"a": 1}, "a") == 1

    def test_nested(self) -> None:
        data = {"image": {"tag": "6.7.0"}}
        assert _get_nested(data, "image.tag") == "6.7.0"

    def test_missing(self) -> None:
        assert _get_nested({"a": 1}, "b") is None

    def test_deep_missing(self) -> None:
        assert _get_nested({"a": {"b": 1}}, "a.c") is None

    def test_non_dict_intermediate(self) -> None:
        assert _get_nested({"a": "string"}, "a.b") is None


class TestGenerateModuleSchema:
    def test_basic_generation(self) -> None:
        computed = {
            "ilum-jupyter": {
                "enabled": True,
                "replicaCount": 1,
                "image": {"tag": "6.7.0", "repository": "ilum/jupyter"},
            }
        }
        defaults = {
            "ilum-jupyter": {
                "enabled": True,
                "replicaCount": 1,
                "image": {"tag": "6.6.0", "repository": "ilum/jupyter"},
            }
        }
        schema = generate_module_schema("jupyter", "ilum-jupyter", computed, defaults, None)
        assert schema.module_name == "jupyter"
        paths = [f.path for f in schema.fields]
        assert "replicaCount" in paths
        assert "image.tag" in paths
        assert "image.repository" in paths
        assert "enabled" in paths

    def test_registry_overrides_auto(self) -> None:
        computed = {
            "ilum-jupyter": {
                "replicaCount": 2,
                "resources": {"requests": {"memory": "4Gi"}},
            }
        }
        registry = ConfigSchema(
            module_name="jupyter",
            fields=(
                ConfigField(
                    path="replicaCount",
                    label="Replica Count",
                    field_type="number",
                    description="Custom description",
                    default=1,
                    min_value=1,
                    max_value=10,
                    tab="general",
                ),
            ),
        )
        schema = generate_module_schema("jupyter", "ilum-jupyter", computed, {}, registry)
        # Registry field should override auto-generated
        replica_field = next(f for f in schema.fields if f.path == "replicaCount")
        assert replica_field.description == "Custom description"
        assert replica_field.min_value == 1
        assert replica_field.max_value == 10

    def test_sensitive_paths_filtered(self) -> None:
        computed = {
            "ilum-jupyter": {
                "adminPassword": "secret123",
                "replicaCount": 1,
            }
        }
        schema = generate_module_schema("jupyter", "ilum-jupyter", computed, {}, None)
        paths = [f.path for f in schema.fields]
        assert "adminPassword" not in paths
        assert "replicaCount" in paths

    def test_registry_can_readd_sensitive(self) -> None:
        computed = {
            "ilum-monitoring": {
                "grafana": {"adminPassword": "admin"},
            }
        }
        registry = ConfigSchema(
            module_name="monitoring",
            fields=(
                ConfigField(
                    path="grafana.adminPassword",
                    label="Grafana Admin Password",
                    field_type="password",
                    description="Admin password.",
                    default="admin",
                    tab="advanced",
                ),
            ),
        )
        schema = generate_module_schema("monitoring", "ilum-monitoring", computed, {}, registry)
        paths = [f.path for f in schema.fields]
        assert "grafana.adminPassword" in paths

    def test_hidden_fields_excluded(self) -> None:
        computed = {
            "ilum-jupyter": {
                "replicaCount": 1,
                "internalSetting": "foo",
            }
        }
        registry = ConfigSchema(
            module_name="jupyter",
            fields=(
                ConfigField(
                    path="internalSetting",
                    label="Internal",
                    field_type="text",
                    description="",
                    hidden=True,
                ),
            ),
        )
        schema = generate_module_schema("jupyter", "ilum-jupyter", computed, {}, registry)
        paths = [f.path for f in schema.fields]
        assert "internalSetting" not in paths

    def test_empty_values_returns_registry_only(self) -> None:
        registry = ConfigSchema(
            module_name="jupyter",
            fields=(
                ConfigField(
                    path="replicaCount",
                    label="Replica Count",
                    field_type="number",
                    description="Replicas.",
                    default=1,
                    tab="general",
                ),
            ),
        )
        schema = generate_module_schema("jupyter", "ilum-jupyter", {}, {}, registry)
        assert len(schema.fields) == 1
        assert schema.fields[0].path == "replicaCount"

    def test_empty_everything_returns_empty(self) -> None:
        schema = generate_module_schema("jupyter", "ilum-jupyter", {}, {}, None)
        assert len(schema.fields) == 0

    def test_fields_sorted_by_tab_then_path(self) -> None:
        computed = {
            "test-mod": {
                "replicaCount": 1,
                "resources": {"requests": {"memory": "2Gi"}},
                "persistence": {"size": "10Gi"},
                "customOption": "value",
            }
        }
        schema = generate_module_schema("test", "test-mod", computed, {}, None)
        tabs = [f.tab for f in schema.fields]
        tab_order = {"general": 0, "resources": 1, "storage": 2, "advanced": 3}
        tab_indices = [tab_order.get(t, 99) for t in tabs]
        assert tab_indices == sorted(tab_indices)

    def test_defaults_populated_from_chart_defaults(self) -> None:
        computed = {"ilum-jupyter": {"image": {"tag": "6.7.0"}}}
        defaults = {"ilum-jupyter": {"image": {"tag": "6.6.0"}}}
        schema = generate_module_schema("jupyter", "ilum-jupyter", computed, defaults, None)
        tag_field = next(f for f in schema.fields if f.path == "image.tag")
        assert tag_field.default == "6.6.0"

    def test_lists_are_skipped(self) -> None:
        computed = {
            "ilum-jupyter": {
                "ports": [8080, 9090],
                "name": "jupyter",
            }
        }
        schema = generate_module_schema("jupyter", "ilum-jupyter", computed, {}, None)
        paths = [f.path for f in schema.fields]
        assert "ports" not in paths
        assert "name" in paths
