#!/usr/bin/env python3
"""
Check the link:/copy: entries in ~/network_drives/home/linux_home_links.yaml
for local files that would block create-automatic-links-in-home.service, let
the user resolve any conflicts, then restart the service.

For each entry the effective destination/source paths are resolved exactly
the way /opt/pli/bin/create-automatic-links-in-home resolves them:
  - link:  destination = ~/<key>            source = (destination.parent / to)
  - copy:  destination = ~/<key>            source = ~/<from>

A destination is "free" if nothing exists there, or if it is already a
symlink pointing at the resolved source. Otherwise its content is diffed
against the source. An empty diff is auto-resolved (the blocking file is
simply removed, since it is byte-identical to the source it would be
replaced by). A non-empty diff is shown to the user, who can:
  d - delete the blocking file (source wins)
  c - copy the blocking file to the source location, then delete it (local
      copy wins, and gets pushed back to the shared source)
  a - abort without touching anything or restarting the service
"""

import difflib
import filecmp
import os
import subprocess
import sys
from pathlib import Path

import yaml

HOME = Path.home()
CONFIG_CANDIDATES = [
    HOME / "network_drives/home/linux_home_links.yaml",
    HOME / "network_drives/home/.linux_home_links.yaml",
]


def load_config():
    for path in CONFIG_CANDIDATES:
        if path.is_file():
            with path.open() as f:
                return path, (yaml.safe_load(f) or {})
    print("error: no linux_home_links.yaml found under ~/network_drives/home/", file=sys.stderr)
    sys.exit(1)


def show_diff(dest, source, dest_text, source_text):
    diff = difflib.unified_diff(
        source_text.splitlines(keepends=True),
        dest_text.splitlines(keepends=True),
        fromfile=str(source),
        tofile=str(dest),
    )
    sys.stdout.writelines(diff)


def read_text_safely(path):
    try:
        return path.read_text(errors="replace")
    except (UnicodeDecodeError, OSError):
        return None


def resolve_conflict(dest: Path, source: Path) -> bool:
    """Return True if resolved (or aborted-through, caller checks), False if user aborted."""
    print(f"\nConflict: {dest}")
    print(f"  blocks link/copy from source: {source}")

    dest_text = read_text_safely(dest) if dest.is_file() else None
    source_text = read_text_safely(source) if source.is_file() else None

    if dest_text is not None and source_text is not None:
        show_diff(dest, source, dest_text, source_text)
    else:
        print("  (binary or unreadable content, diff not shown)")

    while True:
        choice = input("  [d]elete blocking file / [c]opy it to source / [a]bort? ").strip().lower()
        if choice == "d":
            dest.unlink()
            print(f"  deleted {dest}")
            return True
        if choice == "c":
            source.parent.mkdir(parents=True, exist_ok=True)
            data = dest.read_bytes()
            source.write_bytes(data)
            dest.unlink()
            print(f"  copied {dest} -> {source}, then removed {dest}")
            return True
        if choice == "a":
            return False
        print("  please answer d, c, or a")


def check_destination(dest: Path, source: Path, resolved_target_str: str = None) -> bool:
    """Return True if clear (or successfully resolved), False if the user aborted."""
    if dest.is_symlink():
        if resolved_target_str is not None and os.readlink(dest) == resolved_target_str:
            return True  # already correctly linked
        # symlink pointing elsewhere (or broken) - treat its (possibly
        # unreadable) target as the blocking content
        if not source.exists():
            print(f"\nWarning: source missing for {dest} -> {source}; leaving symlink as-is")
            return True
        if dest.exists() and filecmp.cmp(dest, source, shallow=False):
            dest.unlink()
            print(f"identical, removed stale symlink: {dest}")
            return True
        return resolve_conflict(dest, source)

    if not dest.exists():
        return True  # free

    # regular file (or dir) sitting where a link/copy should go
    if not source.exists():
        print(f"\nWarning: source missing for {dest} -> {source}; leaving blocking file as-is")
        return True

    if dest.is_file() and source.is_file() and filecmp.cmp(dest, source, shallow=False):
        dest.unlink()
        print(f"identical, removed redundant file: {dest}")
        return True

    return resolve_conflict(dest, source)


def main():
    config_path, config = load_config()
    print(f"using {config_path}")

    ok = True

    for key, entry in (config.get("link") or {}).items():
        dest = HOME / key
        to = entry["to"]
        source = (dest.parent / to).resolve()
        if not check_destination(dest, source, resolved_target_str=to):
            ok = False

    for key, entry in (config.get("copy") or {}).items():
        dest = HOME / key
        source = (HOME / entry["from"]).resolve()
        if not check_destination(dest, source):
            ok = False

    if not ok:
        print("\nAborted: not all conflicts were resolved, service not restarted.")
        sys.exit(1)

    print("\nAll link/copy targets are free or clear. Restarting service...")
    subprocess.run(
        ["systemctl", "--user", "restart", "create-automatic-links-in-home.service"],
        check=True,
    )
    print("done.")


if __name__ == "__main__":
    main()
