#!/usr/bin/env python3
# Good opsec! You opened the script before running it. Congratulations, you beautiful suspicious bastard. <3
# Read the whole thing: it downloads ONE pinned ZIP from flaptown.com, checks SHA-256,
# installs only LiquidicVibes, and keeps a backup. No admin, telemetry, shell commands,
# credentials, startup hooks, or system-wide changes. Python 3.9+; standard library only.
"""Install Liquidic Vibes on Windows, macOS, or Linux/Wine. Close WoW first."""
import argparse
import hashlib
import io
import os
from pathlib import Path, PurePosixPath
import stat
import sys
import tempfile
import urllib.request
import zipfile

VERSION = '1.0.0'
URL = 'https://flaptown.com/store/files/liquidic-vibes-1.0.0.zip'
SHA256 = 'db1b97229338f912e848aa328c563a442b1133b0b220b67359030142202bad23'
LIMIT = 32 * 1024 * 1024
ADDON = 'LiquidicVibes'


def normalize(path):
    p = Path(path).expanduser().resolve()
    if p.name.lower() == 'addons' and p.parent.name.lower() == 'interface':
        p = p.parent.parent
    elif p.name.lower() != '_forever_' and (p / '_forever_').is_dir():
        p = p / '_forever_'
    if p.name.lower() != '_forever_' or not p.is_dir():
        raise ValueError('Choose the existing World of Warcraft/_forever_ directory (or its Interface/AddOns folder).')
    return p


def discover():
    home = Path.home()
    guesses = []
    # Native Battle.net installs, including common secondary Windows drives.
    for env in ('ProgramFiles(x86)', 'ProgramFiles', 'ProgramW6432'):
        if os.environ.get(env): guesses.append(Path(os.environ[env]) / 'World of Warcraft/_forever_')
    if os.name == 'nt':
        for drive in 'CDEFGHIJKLMNOPQRSTUVWXYZ':
            for folder in ('Program Files (x86)', 'Program Files', 'Games', ''):
                guesses.append(Path(drive + ':/') / folder / 'World of Warcraft/_forever_')
    guesses += [Path('/Applications/World of Warcraft/_forever_'), home / 'Applications/World of Warcraft/_forever_']
    # Common Wine, Lutris, Bottles, CrossOver and Steam/Proton prefixes. No whole-disk scan.
    prefixes = [home / '.wine']
    if os.environ.get('WINEPREFIX'): prefixes.append(Path(os.environ['WINEPREFIX']))
    for base, pattern in (
        (home / 'Games', '*'), (home / 'Games', '*/*'),
        (home / '.local/share/bottles/bottles', '*'),
        (home / '.var/app/com.usebottles.bottles/data/bottles/bottles', '*'),
        (home / 'Library/Application Support/CrossOver/Bottles', '*'),
        (home / '.steam/steam/steamapps/compatdata', '*/pfx'),
        (home / '.local/share/Steam/steamapps/compatdata', '*/pfx'),
    ):
        if base.is_dir(): prefixes.extend(base.glob(pattern))
    for prefix in prefixes:
        for folder in ('Program Files (x86)', 'Program Files', 'Games'):
            guesses.append(prefix / 'drive_c' / folder / 'World of Warcraft/_forever_')
    return sorted({p.resolve() for p in guesses if p.is_dir()}, key=str)


def payload(data):
    if hashlib.sha256(data).hexdigest() != SHA256:
        raise ValueError('SHA-256 mismatch. Nothing installed. Get the installer and ZIP from the same release.')
    result = {}
    with zipfile.ZipFile(io.BytesIO(data)) as archive:
        total = 0
        for item in archive.infolist():
            p = PurePosixPath(item.filename)
            if (p.is_absolute() or '..' in p.parts or '\\' in item.filename or ':' in item.filename
                    or not p.parts or p.parts[0] != ADDON or stat.S_ISLNK(item.external_attr >> 16)):
                raise ValueError('Unsafe ZIP entry; nothing installed.')
            total += item.file_size
            if total > LIMIT: raise ValueError('Expanded ZIP exceeds the safety bound.')
            if item.is_dir(): continue
            relative = str(PurePosixPath(*p.parts[1:]))
            if relative in result: raise ValueError('Duplicate ZIP entry.')
            result[relative] = archive.read(item)
    required = {'LiquidicVibes.toc', 'LiquidicVibes.lua', 'Media/wowunderwater.ogg'}
    if not required.issubset(result): raise ValueError('Incomplete addon ZIP.')
    return result


def install(wow, files):
    addons = wow / 'Interface' / 'AddOns'
    # Never follow an AddOns symlink outside the chosen game installation.
    if not addons.resolve().is_relative_to(wow):
        raise ValueError('Interface/AddOns points outside the chosen game. Use manual installation.')
    addons.mkdir(parents=True, exist_ok=True)
    dest = addons / ADDON
    if dest.is_symlink(): raise ValueError('Existing addon is a symlink. Use manual installation.')
    backup = None
    # Temporary files on the same filesystem allow an atomic final rename.
    with tempfile.TemporaryDirectory(prefix='.liquidic-vibes-', dir=wow) as temporary:
        staged = Path(temporary) / ADDON
        staged.mkdir()
        for relative, data in files.items():
            target = staged / relative
            target.parent.mkdir(parents=True, exist_ok=True)
            target.write_bytes(data)
        if dest.exists():
            backups = wow / 'LiquidicVibes-backups'
            if backups.is_symlink(): raise ValueError('Backup directory is a symlink; nothing replaced.')
            backups.mkdir(exist_ok=True)
            backup = Path(tempfile.mkdtemp(prefix='previous-', dir=backups)) / ADDON
            dest.rename(backup)
        try:
            staged.rename(dest)
        except BaseException:
            if backup is not None and not dest.exists(): backup.rename(dest)
            raise
    return dest, backup


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--wow-dir', help='World of Warcraft, _forever_, or Interface/AddOns folder')
    parser.add_argument('--archive', type=Path, help='Use a downloaded release ZIP instead of the network')
    parser.add_argument('--list', action='store_true', help='List detected locations; do not download or install')
    args = parser.parse_args()
    found = discover() if not args.wow_dir or args.list else []
    if args.list:
        print('\n'.join(map(str, found)) or 'No common installation found. Use --wow-dir.')
        return
    if args.wow_dir:
        wow = normalize(args.wow_dir)
    elif len(found) == 1:
        wow = normalize(found[0])
    else:
        for p in found: print(p)
        raise ValueError('No unique installation found. Run again with --wow-dir "path/to/_forever_".')
    print('Installing into:', wow)
    if args.archive:
        if args.archive.stat().st_size > LIMIT: raise ValueError('ZIP exceeds the safety bound.')
        data = args.archive.read_bytes()
    else:
        request = urllib.request.Request(URL, headers={'User-Agent': 'LiquidicVibes-Installer/' + VERSION})
        with urllib.request.urlopen(request, timeout=60) as response:
            if not response.url.startswith('https://flaptown.com/'):
                raise ValueError('Unexpected download redirect; nothing installed.')
            data = response.read(LIMIT + 1)
        if len(data) > LIMIT: raise ValueError('Download exceeds the safety bound.')
    dest, backup = install(wow, payload(data))
    print('Installed:', dest)
    if backup: print('Previous version kept at:', backup)
    if (wow / 'Interface/AddOns/UnderwaterMusic').exists():
        print('Disable the old Underwater Music addon in-game to avoid duplicate playback.')
    print('Restart WoW, enable Liquidic Vibes and Music, then try /lvibes test. Dive in. <3')


if __name__ == '__main__':
    try:
        main()
    except (OSError, ValueError, zipfile.BadZipFile) as error:
        print('Installation stopped:', error, file=sys.stderr)
        sys.exit(1)
