from collections.abc import Callable
from io import BytesIO
from pathlib import Path
from typing import Any, cast

import yaml

from mmar_envops.exec_context import ExecContext
from mmar_envops.stdout import build_diff


# todo extract to YAMLAccessor somehow, also eliminate duplication in migrations
# this is really needable, because standard load/dump approach is bad: there is no bak, no respect to comments
def patch_yaml(ectx: ExecContext, yaml_path: Path, patch: Callable[[dict], dict | None]):
    yaml_old = yaml_path.with_suffix(".old.yaml")
    yaml_text = yaml_path.read_text().strip()
    data = load_from_yaml(yaml_text)
    data_patched = patch(data)
    if data_patched is None:
        print("No changes: patch returned None")
        return
    yaml_text_fix = dump_to_yaml(data_patched)
    if yaml_text_fix == yaml_text:
        print(f"No changes: YAML result at {yaml_path} after patch is not changed")
        return
    if not ectx.dry:
        yaml_old.write_text(yaml_text)
        yaml_path.write_text(yaml_text_fix)
        print(f"Saved to {yaml_path}")
        print(f"Saved old version to {yaml_old}")
    diff = build_diff(text_old=yaml_text, text=yaml_text_fix, col=ectx.col)
    print(diff)


class IndentDumper(yaml.Dumper):
    def increase_indent(self, flow=False, indentless=False):
        return super(IndentDumper, self).increase_indent(flow, False)


def load_from_yaml(content) -> dict:
    stream = BytesIO(content.encode())
    return yaml.safe_load(stream) or {}


def dump_to_yaml(data: dict) -> str:
    res: Any = yaml.dump(
        data=data,
        default_flow_style=None,
        sort_keys=False,
        allow_unicode=True,
        Dumper=IndentDumper,
    )
    res = res.replace("'<<': '*common'", "<<: *common")
    return cast(str, res).strip()
