import shutil
from pathlib import Path
from typing import Callable

from l4d2host.paths import get_left4me_root
from l4d2host.process import run_command
from l4d2host.logging import emit_step


def _resolve_steamcmd_siblings(steamcmd: str) -> tuple[Path, Path] | None:
    resolved = shutil.which(steamcmd)
    base = Path(resolved).resolve() if resolved else Path(steamcmd)
    if base.is_symlink():
        base = base.resolve()
    parent = base.parent
    linux32 = parent / "linux32"
    linux64 = parent / "linux64"
    if linux32.is_dir() and linux64.is_dir():
        return linux32, linux64
    return None


def _set_symlink(link: Path, target: Path, on_stderr: Callable[[str], None] | None) -> None:
    if link.is_symlink():
        try:
            current = link.resolve()
        except OSError:
            current = None
        if current == target.resolve():
            return
        link.unlink()
    elif link.exists():
        if on_stderr is not None:
            on_stderr(f"refusing to replace non-symlink at {link}")
        return
    link.symlink_to(target)


def _ensure_steam_sdk_symlinks(
    install_dir: Path,
    steamcmd: str,
    *,
    on_stdout: Callable[[str], None] | None,
    on_stderr: Callable[[str], None] | None,
    passthrough: bool,
) -> None:
    emit_step("ensuring steam sdk symlinks...", on_stdout, passthrough)
    siblings = _resolve_steamcmd_siblings(steamcmd)
    if siblings is not None:
        sdk32_target, sdk64_target = siblings
    else:
        fallback = install_dir / "bin"
        sdk32_target, sdk64_target = fallback, fallback

    steam_dir = Path.home() / ".steam"
    steam_dir.mkdir(parents=True, exist_ok=True)
    _set_symlink(steam_dir / "sdk32", sdk32_target, on_stderr)
    _set_symlink(steam_dir / "sdk64", sdk64_target, on_stderr)


class SteamInstaller:
    def __init__(self, install_dir: Path | None = None, steamcmd: str = "steamcmd"):
        self.install_dir = get_left4me_root() / "installation" if install_dir is None else Path(install_dir)
        self.steamcmd = steamcmd

    def install_or_update(
        self,
        *,
        on_stdout: Callable[[str], None] | None = None,
        on_stderr: Callable[[str], None] | None = None,
        passthrough: bool = False,
        should_cancel: Callable[[], bool] | None = None,
    ) -> None:
        for platform in ("windows", "linux"):
            emit_step(f"downloading {platform} platform payload...", on_stdout, passthrough)
            run_command(
                [
                    self.steamcmd,
                    "+force_install_dir",
                    str(self.install_dir),
                    "+login",
                    "anonymous",
                    "+@sSteamCmdForcePlatformType",
                    platform,
                    "+app_update",
                    "222860",
                    "validate",
                    "+quit",
                ],
                on_stdout=on_stdout,
                on_stderr=on_stderr,
                passthrough=passthrough,
                should_cancel=should_cancel,
            )
        _ensure_steam_sdk_symlinks(
            self.install_dir,
            self.steamcmd,
            on_stdout=on_stdout,
            on_stderr=on_stderr,
            passthrough=passthrough,
        )
        emit_step("installation complete.", on_stdout, passthrough)
