#!/usr/bin/env python3

__package__ = 'archivebox.cli'

import os
import sys
from typing import Optional, List

import rich_click as click
from rich import print

from archivebox.misc.util import docstring, enforce_types


@enforce_types
def install(binproviders: Optional[List[str]]=None, binaries: Optional[List[str]]=None, dry_run: bool=False) -> None:
    """Automatically install all ArchiveBox dependencies and extras"""
    
    # if running as root:
    #    - run init to create index + lib dir
    #    - chown -R 911 DATA_DIR
    #    - install all binaries as root
    #    - chown -R 911 LIB_DIR
    # else:
    #    - run init to create index + lib dir as current user
    #    - install all binaries as current user
    #    - recommend user re-run with sudo if any deps need to be installed as root

    import abx
    import archivebox
    from archivebox.config.permissions import IS_ROOT, ARCHIVEBOX_USER, ARCHIVEBOX_GROUP, SudoPermission
    from archivebox.config.paths import DATA_DIR, ARCHIVE_DIR, get_or_create_working_lib_dir
    from archivebox.misc.logging import stderr
    from archivebox.cli.archivebox_init import init
    from archivebox.misc.system import run as run_shell


    if not (os.access(ARCHIVE_DIR, os.R_OK) and ARCHIVE_DIR.is_dir()):
        init()  # must init full index because we need a db to store InstalledBinary entries in

    print('\n[green][+] Installing ArchiveBox dependencies automatically...[/green]')
    
    # we never want the data dir to be owned by root, detect owner of existing owner of DATA_DIR to try and guess desired non-root UID
    if IS_ROOT:
        EUID = os.geteuid()
        
        # if we have sudo/root permissions, take advantage of them just while installing dependencies
        print()
        print(f'[yellow]:warning:  Running as UID=[blue]{EUID}[/blue] with [red]sudo[/red] only for dependencies that need it.[/yellow]')
        print(f'    DATA_DIR, LIB_DIR, and TMP_DIR will be owned by [blue]{ARCHIVEBOX_USER}:{ARCHIVEBOX_GROUP}[/blue].')
        print()
    
    LIB_DIR = get_or_create_working_lib_dir()
    
    package_manager_names = ', '.join(
        f'[yellow]{binprovider.name}[/yellow]'
        for binprovider in reversed(list(abx.as_dict(abx.pm.hook.get_BINPROVIDERS()).values()))
        if not binproviders or (binproviders and binprovider.name in binproviders)
    )
    print(f'[+] Setting up package managers {package_manager_names}...')
    for binprovider in reversed(list(abx.as_dict(abx.pm.hook.get_BINPROVIDERS()).values())):
        if binproviders and binprovider.name not in binproviders:
            continue
        try:
            binprovider.setup()
        except Exception:
            # it's ok, installing binaries below will automatically set up package managers as needed
            # e.g. if user does not have npm available we cannot set it up here yet, but once npm Binary is installed
            # the next package that depends on npm will automatically call binprovider.setup() during its own install
            pass
    
    print()
    
    for binary in reversed(list(abx.as_dict(abx.pm.hook.get_BINARIES()).values())):
        if binary.name in ('archivebox', 'django', 'sqlite', 'python'):
            # obviously must already be installed if we are running
            continue
        
        if binaries and binary.name not in binaries:
            continue
        
        providers = ' [grey53]or[/grey53] '.join(
            provider.name for provider in binary.binproviders_supported
            if not binproviders or (binproviders and provider.name in binproviders)
        )
        if not providers:
            continue
        print(f'[+] Detecting / Installing [yellow]{binary.name.ljust(22)}[/yellow] using [red]{providers}[/red]...')
        try:
            with SudoPermission(uid=0, fallback=True):
                # print(binary.load_or_install(fresh=True).model_dump(exclude={'overrides', 'bin_dir', 'hook_type'}))
                if binproviders:
                    providers_supported_by_binary = [provider.name for provider in binary.binproviders_supported]
                    for binprovider_name in binproviders:
                        if binprovider_name not in providers_supported_by_binary:
                            continue
                        try:
                            if dry_run:
                                # always show install commands when doing a dry run
                                sys.stderr.write("\033[2;49;90m")  # grey53
                                result = binary.install(binproviders=[binprovider_name], dry_run=dry_run).model_dump(exclude={'overrides', 'bin_dir', 'hook_type'})
                                sys.stderr.write("\033[00m\n")     # reset
                            else:
                                loaded_binary = archivebox.pm.hook.binary_load_or_install(binary=binary, binproviders=[binprovider_name], fresh=True, dry_run=dry_run, quiet=False)
                                result = loaded_binary.model_dump(exclude={'overrides', 'bin_dir', 'hook_type'})
                            if result and result['loaded_version']:
                                break
                        except Exception as e:
                            print(f'[red]:cross_mark: Failed to install {binary.name} as using {binprovider_name} as user {ARCHIVEBOX_USER}: {e}[/red]')
                else:
                    if dry_run:
                        sys.stderr.write("\033[2;49;90m")  # grey53
                        binary.install(dry_run=dry_run).model_dump(exclude={'overrides', 'bin_dir', 'hook_type'})
                        sys.stderr.write("\033[00m\n")  # reset
                    else:
                        loaded_binary = archivebox.pm.hook.binary_load_or_install(binary=binary, fresh=True, dry_run=dry_run)
                        result = loaded_binary.model_dump(exclude={'overrides', 'bin_dir', 'hook_type'})
            if IS_ROOT and LIB_DIR:
                with SudoPermission(uid=0):
                    if ARCHIVEBOX_USER == 0:
                        os.system(f'chmod -R 777 "{LIB_DIR.resolve()}"')
                    else:    
                        os.system(f'chown -R {ARCHIVEBOX_USER} "{LIB_DIR.resolve()}"')
        except Exception as e:
            print(f'[red]:cross_mark: Failed to install {binary.name} as user {ARCHIVEBOX_USER}: {e}[/red]')
            if binaries and len(binaries) == 1:
                # if we are only installing a single binary, raise the exception so the user can see what went wrong
                raise
                
    from archivebox.config.django import setup_django
    setup_django()
    
    from django.contrib.auth import get_user_model
    User = get_user_model()

    if not User.objects.filter(is_superuser=True).exclude(username='system').exists():
        stderr('\n[+] Don\'t forget to create a new admin user for the Web UI...', color='green')
        stderr('    archivebox manage createsuperuser')
        # run_subcommand('manage', subcommand_args=['createsuperuser'], pwd=out_dir)
    
    print('\n[green][√] Set up ArchiveBox and its dependencies successfully.[/green]\n', file=sys.stderr)
    
    from abx_plugin_pip.binaries import ARCHIVEBOX_BINARY
    
    extra_args = []
    if binproviders:
        extra_args.append(f'--binproviders={",".join(binproviders)}')
    if binaries:
        extra_args.append(f'--binaries={",".join(binaries)}')
    
    proc = run_shell([ARCHIVEBOX_BINARY.load().abspath, 'version', *extra_args], capture_output=False, cwd=DATA_DIR)
    raise SystemExit(proc.returncode)


@click.command()
@click.option('--binproviders', '-p', type=str, help='Select binproviders to use DEFAULT=env,apt,brew,sys_pip,venv_pip,lib_pip,pipx,sys_npm,lib_npm,puppeteer,playwright (all)', default=None)
@click.option('--binaries', '-b', type=str, help='Select binaries to install DEFAULT=curl,wget,git,yt-dlp,chrome,single-file,readability-extractor,postlight-parser,... (all)', default=None)
@click.option('--dry-run', '-d', is_flag=True, help='Show what would be installed without actually installing anything', default=False)
@docstring(install.__doc__)
def main(**kwargs) -> None:
    install(**kwargs)
    

if __name__ == '__main__':
    main()