"""set_db_data implementation."""

from __future__ import annotations

from pathlib import Path
from typing import Any

from db_test_helpers.core._errors import UnsupportedError
from db_test_helpers.core._matchers import is_dsl_string, is_var_dsl, resolve_var
from db_test_helpers.core._parse import ParsedGroup, ParsedRecord, parse_yaml
from db_test_helpers.core._types import Config, DatabaseAdapter, HierarchicalAdapter


def set_db_data(adapter: DatabaseAdapter, filepath: str | Path, config: Config | None = None) -> None:
    """Clear groups and write records described in a YAML fixture file.

    Args:
        adapter: Database adapter used to perform clear/write operations.
        filepath: Path to the YAML fixture file.
        config: Optional configuration (e.g. variables for ``$var()``).

    Raises:
        ValueError: If the YAML file is invalid or contains unsupported DSL
            functions (only ``$var()`` is allowed in set data).
        UnsupportedError: If ``__children__`` is used but the adapter does not
            implement ``HierarchicalAdapter``.
    """
    groups = parse_yaml(filepath)
    variables = config.variables if config else {}
    _process_groups(adapter, groups, "", variables)


def _process_groups(
    adapter: DatabaseAdapter,
    groups: list[ParsedGroup],
    parent_path: str,
    variables: dict[str, object],
) -> None:
    for group in groups:
        adapter.clear_group(group.name, parent_path)
        for record in group.records:
            _process_record(adapter, group.name, parent_path, record, variables)


def _process_record(
    adapter: DatabaseAdapter,
    group_name: str,
    parent_path: str,
    record: ParsedRecord,
    variables: dict[str, object],
) -> None:
    data = _resolve_for_set(record.fields, variables)
    actual = adapter.write_record(group_name, parent_path, data)

    if record.children:
        if not isinstance(adapter, HierarchicalAdapter):
            raise UnsupportedError(
                f"{type(adapter).__name__} does not support __children__"
            )
        child_path = adapter.build_child_path(parent_path, group_name, actual)
        child_groups = [ParsedGroup(name=name, records=records) for name, records in record.children.items()]
        _process_groups(adapter, child_groups, child_path, variables)


def _resolve_for_set(data: Any, variables: dict[str, object]) -> Any:
    if isinstance(data, dict):
        return {k: _resolve_for_set(v, variables) for k, v in data.items()}
    if isinstance(data, list):
        return [_resolve_for_set(item, variables) for item in data]
    if isinstance(data, str):
        if is_var_dsl(data):
            return resolve_var(data, variables)
        if is_dsl_string(data):
            raise ValueError(f"DSL function {data!r} is not allowed in set data. Only $var() is allowed.")
    return data
