# coding: utf-8
#

from __future__ import absolute_import, print_function

import argparse
import hashlib
import logging
import os
import re
import shutil
import sys
import tarfile
import time

import fire
import humanize
import progress.bar
import requests
from logzero import logger
from retry import retry

import uiautomator2 as u2
from uiautomator2 import adbutils
from uiautomator2.version import __apk_version__, __atx_agent_version__

appdir = os.path.join(os.path.expanduser("~"), '.uiautomator2')
logger.debug("use cache directory: %s", appdir)

GITHUB_BASEURL = "https://github.com/openatx"


class DownloadBar(progress.bar.Bar):
    message = "Downloading"
    suffix = '%(current_size)s / %(total_size)s'

    @property
    def total_size(self):
        return humanize.naturalsize(self.max, gnu=True)

    @property
    def current_size(self):
        return humanize.naturalsize(self.index, gnu=True)


def cache_download(url, filename=None, timeout=None):
    """ return downloaded filepath """
    # check cache
    if not filename:
        filename = os.path.basename(url)
    storepath = os.path.join(appdir,
                             hashlib.sha224(url.encode()).hexdigest(),
                             filename)
    storedir = os.path.dirname(storepath)
    if not os.path.isdir(storedir):
        os.makedirs(storedir)
    if os.path.exists(storepath) and os.path.getsize(storepath) > 0:
        return storepath
    # download from url
    headers = {
        'Accept': '*/*',
        'Accept-Encoding': 'gzip, deflate, br',
        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
        'Connection': 'keep-alive',
        'Origin': 'https://github.com',
        'User-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'
    } # yapf: disable
    r = requests.get(url, stream=True, headers=headers, timeout=None)
    r.raise_for_status()

    file_size = int(r.headers.get("Content-Length"))
    bar = DownloadBar(filename, max=file_size)
    with open(storepath + '.tmp', 'wb') as f:
        chunk_length = 16 * 1024
        while 1:
            buf = r.raw.read(chunk_length)
            if not buf:
                break
            f.write(buf)
            bar.next(len(buf))
        bar.finish()
    shutil.move(storepath + '.tmp', storepath)
    return storepath


def mirror_download(url, filename: str):
    github_host = "https://github.com"
    if url.startswith(github_host):
        mirror_url = "http://tool.appetizer.io" + url[len(
            github_host):]  # mirror of github
        try:
            return cache_download(mirror_url, filename, timeout=60)
        except requests.RequestException as e:
            logger.debug("download from mirror error, use origin source")

    return cache_download(url, filename)


class Initer():
    def __init__(self, device):
        logger.info(">>> Initial device %s", device)
        d = self._device = device

        self.sdk = d.getprop('ro.build.version.sdk')
        self.abi = d.getprop('ro.product.cpu.abi')
        self.pre = d.getprop('ro.build.version.preview_sdk')
        self.arch = d.getprop('ro.arch')
        self.abis = (d.getprop('ro.product.cpu.abilist').strip()
                     or self.abi).split(",")
        self.server_addr = None

    def shell(self, *args):
        logger.debug("Shell: %s", args)
        return self._device.shell(args)

    @property
    def apk_urls(self):
        for name in ["app-uiautomator.apk", "app-uiautomator-test.apk"]:
            yield "".join([
                GITHUB_BASEURL,
                "/android-uiautomator-server/releases/download/",
                __apk_version__, "/", name
            ])

    @property
    def atx_agent_url(self):
        files = {
            'armeabi-v7a': 'atx-agent_{v}_linux_armv7.tar.gz',
            'arm64-v8a': 'atx-agent_{v}_linux_armv7.tar.gz',
            'armeabi': 'atx-agent_{v}_linux_armv6.tar.gz',
            'x86': 'atx-agent_{v}_linux_386.tar.gz',
        }
        name = None
        for abi in self.abis:
            name = files.get(abi)
            if name:
                break
        if not name:
            raise Exception(
                "arch(%s) need to be supported yet, please report an issue in github"
                % self.abis)
        return GITHUB_BASEURL + '/atx-agent/releases/download/%s/%s' % (
            __atx_agent_version__, name.format(v=__atx_agent_version__))

    @property
    def minicap_urls(self):
        base_url = GITHUB_BASEURL + \
            "/stf-binaries/raw/master/node_modules/minicap-prebuilt/prebuilt/"
        sdk = self.sdk
        yield base_url + self.abi + "/lib/android-" + sdk + "/minicap.so"
        yield base_url + self.abi + "/bin/minicap"

    @property
    def minitouch_url(self):
        return ''.join([
            GITHUB_BASEURL + "/stf-binaries",
            "/raw/master/node_modules/minitouch-prebuilt/prebuilt/",
            self.abi + "/bin/minitouch"
        ])

    def push_url(self, url, dest=None, mode=0o755, tgz=False, extract_name=None):  # yapf: disable
        path = mirror_download(url, os.path.basename(url))
        if tgz:
            tar = tarfile.open(path, 'r:gz')
            path = os.path.join(os.path.dirname(path), extract_name)
            tar.extract(extract_name, os.path.dirname(path))
        if not dest:
            dest = "/data/local/tmp/" + os.path.basename(path)

        logger.debug("Push %s -> %s:0%o", url, dest, mode)
        self._device.sync.push(path, dest, mode=mode)
        return dest

    def is_apk_outdate(self):
        apk1 = self._device.package_info("com.github.uiautomator")
        if not apk1:
            return True
        if apk1['version_name'] != __apk_version__:
            return True
        if not self._device.package_info("com.github.uiautomator.test"):
            return True
        return False

    def install(self, server_addr=None):
        logger.info("Install minicap, minitouch")
        self.push_url(self.minitouch_url)
        if self.abi == "x86":
            logger.info(
                "abi:x86 seems to be android emulator, skip install minicap")
        else:
            for url in self.minicap_urls:
                self.push_url(url)

        logger.info(
            "Install com.github.uiautomator, com.github.uiautomator.test")

        if self.is_apk_outdate():
            self.shell("pm", "uninstall", "com.github.uiautomator")
            self.shell("pm", "uninstall", "com.github.uiautomator.test")
            for url in self.apk_urls:
                path = self.push_url(url, mode=0o644)
                self.shell("pm", "install", "-r", "-t", path)
        else:
            logger.info("Already installed com.github.uiautomator apks")

        logger.info("Install atx-agent")
        path = self.push_url(self.atx_agent_url,
                             tgz=True,
                             extract_name="atx-agent")
        args = [path, "server", "-d"]
        if server_addr:
            args.extend(['-t', server_addr])
        self.shell(path, "server", "--stop")
        self.shell(*args)

        logger.info("Check install")
        self.check_atx_agent_version()
        print("Successfully init %s" % self._device)

    @retry(
        (requests.ConnectionError, requests.ReadTimeout, requests.HTTPError),
        delay=.5,
        tries=10)
    def check_atx_agent_version(self):
        port = self._device.forward_port(7912)
        logger.debug("Forward: local:tcp:%d -> remote:tcp:%d", port, 7912)
        response = requests.get("http://127.0.0.1:%d/version" % port).text
        logger.debug("atx-agent version %s", response.strip())

    # def check_apk_installed(self, apk_version):
    #     """ in OPPO device, if you check immediatelly, package_info will return None """
    #     pkg_info = self.package_info("com.github.uiautomator")
    #     if not pkg_info:
    #         raise EnvironmentError(
    #             "package com.github.uiautomator not installed")
    #     if pkg_info['version_name'] != apk_version:
    #         raise EnvironmentError(
    #             "package com.github.uiautomator version expect \"%s\" got \"%s\""
    #             % (apk_version, pkg_info['version_name']))

    #             r = requests.get(
    #                 'http://localhost:%d/version' % lport, timeout=10)
    #             r.raise_for_status()
    #             log.info("atx-agent version: %s", r.text)
    #             # todo finish the retry logic
    #             print("atx-agent output:", output.strip())
    #             # open uiautomator2 github URL
    #             self.shell("am", "start", "-a", "android.intent.action.VIEW",
    #                        "-d", "https://github.com/openatx/uiautomator2")


class MyFire(object):
    def update_apk(self, ip):
        """ update com.github.uiautomator apk remotely """
        u = u2.connect(ip)
        apk_version = __apk_version__
        app_url = GITHUB_BASEURL + \
            '/android-uiautomator-server/releases/download/%s/app-uiautomator.apk' % apk_version
        app_test_url = GITHUB_BASEURL + \
            '/android-uiautomator-server/releases/download/%s/app-uiautomator-test.apk' % apk_version
        u.app_install(app_url)
        u.app_install(app_test_url)

    def clear_cache(self):
        logger.info("clear cache dir: %s", appdir)
        shutil.rmtree(appdir, ignore_errors=True)

    def install(self, arg1, arg2=None):
        """
        Example:
            install "http://some-host.apk"
            install "$serial" "http://some-host.apk"
        """
        if arg2 is None:
            device_ip, apk_url = None, arg1
        else:
            device_ip, apk_url = arg1, arg2
        u = u2.connect(device_ip)
        pkg_name = u.app_install(apk_url)
        print("Installed", pkg_name)

    def unlock(self, device_ip=None):
        u = u2.connect(device_ip)
        u.unlock()

    def app_stop_all(self, device_ip=None):
        u = u2.connect(device_ip)
        u.app_stop_all()

    def uninstall_all(self, device_ip=None):
        u = u2.connect(device_ip)
        u.app_uninstall_all(verbose=True)

    def identify(self, device_ip=None, theme='black'):
        u = u2.connect(device_ip)
        u.open_identify(theme)

    def screenshot(self, device_ip, filename):
        u = u2.connect(device_ip)
        u.screenshot(filename)

    def healthcheck(self, device_ip):
        u = u2.connect(device_ip)
        u.healthcheck()


def cmd_init(args):
    if args.serial:
        device = adbutils.adb.device(args.serial)
        init = Initer(device)
        init.install(args.server)

    else:
        for device in adbutils.adb.iter_device():
            init = Initer(device)
            init.install(args.server)


def cmd_screenshot(args):
    raise NotImplementedError()


_commands = [
    dict(action=cmd_init,
         command="init",
         help="install enssential resources to device",
         flags=[
             dict(args=['--serial', '-s'], type=str, help='serial number'),
             dict(name=['server'], type=str, help='atxserver address')
         ]),
    dict(action=cmd_screenshot,
         command="screenshot",
         help="not implemented",
         flags=[
             dict(args=['-o', '--output'], type=str, help="output filename")
         ])
]


def main():
    # yapf: disable
    if True or len(sys.argv) >= 2 and sys.argv[1] in ('init', 'screenshot'):
        parser = argparse.ArgumentParser(
            formatter_class=argparse.ArgumentDefaultsHelpFormatter)
        parser.add_argument('-s', '--serial', type=str,
                            help='device serial number')

        subparser = parser.add_subparsers(dest='subparser')

        actions = {}
        for c in _commands:
            cmd_name = c['command']
            actions[cmd_name] = c['action']
            sp = subparser.add_parser(cmd_name, help=c.get('help'),
                                      formatter_class=argparse.ArgumentDefaultsHelpFormatter)
            for f in c.get('flags', []):
                args = f.get('args')
                if not args:
                    args = ['-'*min(2, len(n)) + n for n in f['name']]
                kwargs = f.copy()
                kwargs.pop('name', None)
                kwargs.pop('args', None)
                sp.add_argument(*args, **kwargs)

        args = parser.parse_args()
        print(args, args.subparser)

        if args.subparser:
            actions[args.subparser](args)
        return
    # yapf: enable

    fire.Fire(MyFire)


if __name__ == '__main__':
    # import logzero
    # logzero.loglevel(logging.INFO)
    main()
