import os
import json
import shutil
import getpass
import datetime
import platform
import requests
import subprocess


# Developer settings
GITHUB_API_TOKEN = ""
headers = {'Authorization': 'token ' + GITHUB_API_TOKEN}
CUSTOM_HEADERS_ENABLED = False
DEBUGGING = False
TIME_FORMAT = "%Y-%m-%d %H:%M:%S"

# ANISETTE-SERVER
ANISETTE_HOST = "127.0.0.1"
ANISETTE_PORT = 6969

# ARCH
ARCH = platform.machine()
if ARCH == "armv7l":
    ARCH = "armv7"
NETMUXD_AVAILABLE_ARCHS = ("x86_64", "aarch64", "armv7")
NETMUXD_IS_AVAILABLE = ARCH in NETMUXD_AVAILABLE_ARCHS
Netmuxd_is_on = True if NETMUXD_IS_AVAILABLE else False


# DIRECTORY
CURRENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
RESOURCE_DIRECTORY = os.path.join(CURRENT_DIRECTORY, "resource")

# VERSIONS
"""
Versions will be fetch with FetchVersion()
"""
Latest_AltServer_Version = ""
Latest_AltStore_Version = ""
Latest_Netmuxd_Version = ""
Latest_Anisette_Server_Version = ""
Latest_Script_Version = ""

# PATH AND URL
"""
URL value default is "" , value will be update with FetchVersion()
"""
VERSION_JSON_PATH = os.path.join(CURRENT_DIRECTORY, "version.json")

ALTSERVER_PATH = os.path.join(RESOURCE_DIRECTORY, "AltServer")
Altserver_URL = ""

ALTSTORE_PATH = os.path.join(RESOURCE_DIRECTORY, "AltStore.ipa")
AltStore_URL = ""

NETMUXD_PATH = os.path.join(RESOURCE_DIRECTORY, "netmuxd")
Netmuxd_URL = ""

ANISETTE_SERVER_PATH = os.path.join(RESOURCE_DIRECTORY, "anisette-server")
Anisette_Server_URL = ""

SCRIPT_PATH = os.path.join(CURRENT_DIRECTORY, "main.py")
SCRIPT_URL = "https://raw.githubusercontent.com/powenn/AltServer-Linux-PyScript/rewrite/main.py"

# UPDATABLE BOOLS
"""
Default is false , value will be update with CheckUpdate()
"""
AltServer_Is_Updatable = False
AltStore_Is_Updatable = False
Nermuxd_Is_Updatable = False
Anisette_Server_Is_Updatable = False
Script_Is_Updatable = False


Version_Fetched = False


def getAnswer(text):
    try:
        return input(text)
    except KeyboardInterrupt:
        print("\nCtrl+C pressed, aborting")
        exit(-2)


def DebugPrint(msg):
    now = datetime.datetime.now().strftime(TIME_FORMAT)
    if DEBUGGING:
        print(f"[DEBUG] {now}\n== {msg} ==")


def FetchVersion() -> dict:
    print("Fetching version ...")

    global Latest_AltServer_Version, Latest_AltStore_Version, Latest_Anisette_Server_Version, Latest_Netmuxd_Version, Latest_Script_Version

    AltStore_Response = requests.get(
        "https://cdn.altstore.io/file/altstore/apps.json").json()["apps"][0]["versions"][0]
    Latest_AltStore_Version = AltStore_Response["version"]

    Latest_AltServer_Version = requests.get(
        "https://api.github.com/repos/NyaMisty/AltServer-Linux/releases/latest", headers=headers if CUSTOM_HEADERS_ENABLED else "").json()["tag_name"]
    Latest_Netmuxd_Version = requests.get(
        "https://api.github.com/repos/jkcoxson/netmuxd/releases/latest", headers=headers if CUSTOM_HEADERS_ENABLED else "").json()["tag_name"]
    Latest_Anisette_Server_Version = requests.get(
        "https://api.github.com/repos/Dadoum/Provision/releases/latest", headers=headers if CUSTOM_HEADERS_ENABLED else "").json()["tag_name"]
    Latest_Script_Version = requests.get(
        "https://api.github.com/repos/powenn/AltServer-Linux-PyScript/releases/latest", headers=headers if CUSTOM_HEADERS_ENABLED else "").json()["tag_name"]

    global Altserver_URL, AltStore_URL, Netmuxd_URL, Anisette_Server_URL, Version_Fetched

    Altserver_URL = f"https://github.com/NyaMisty/AltServer-Linux/releases/download/{Latest_AltServer_Version}/AltServer-{ARCH}"
    Netmuxd_URL = f"https://github.com/jkcoxson/netmuxd/releases/download/{Latest_Netmuxd_Version}/{ARCH}-linux-netmuxd"
    Anisette_Server_URL = f"https://github.com/Dadoum/Provision/releases/download/{Latest_Anisette_Server_Version}/anisette-server-{ARCH}"
    AltStore_URL = AltStore_Response["downloadURL"]

    Version_Fetched = True

    print("Done")
    json_data = {"AltServer": Latest_AltServer_Version, "AltStore": Latest_AltStore_Version, "Netmuxd": Latest_Netmuxd_Version,
                 "Anisette-Server": Latest_Anisette_Server_Version, "Script": Latest_Script_Version}
    DebugPrint(json_data)
    return json_data


def CheckResource():
    resource_list = os.listdir(RESOURCE_DIRECTORY) if os.path.exists(
        RESOURCE_DIRECTORY) else []
    Resource_Missed = not all(resource in resource_list for resource in [
                              'AltServer', 'anisette-server', 'AltStore.ipa', 'netmuxd'])
    latest_version_json = {}

    DebugPrint(f"RESOURCE_MISSED : {Resource_Missed}")

    if not os.path.exists(VERSION_JSON_PATH):
        print("version.json not exists")
        latest_version_json: dict = FetchVersion()
        DebugPrint(latest_version_json)
        with open(VERSION_JSON_PATH, "w") as outfile:
            json.dump(latest_version_json, outfile)
        # Remove all executable binaries to get new binaries
        if os.path.exists(RESOURCE_DIRECTORY):
            shutil.rmtree(RESOURCE_DIRECTORY)

    if Resource_Missed and not Version_Fetched:
        latest_version_json: dict = FetchVersion()

    global current_version_json
    current_version_json = json.load(open(VERSION_JSON_PATH))
    # Resource dir
    if not os.path.exists(RESOURCE_DIRECTORY):
        print("Creating 'resource' directory")
        os.mkdir(RESOURCE_DIRECTORY)
    # AltServer
    if not os.path.exists(ALTSERVER_PATH):
        print(f"Downloading Altserver {Latest_AltServer_Version}")
        DebugPrint(Altserver_URL)
        response = requests.get(Altserver_URL)
        open(ALTSERVER_PATH, "wb").write(response.content)
        current_version_json["AltServer"] = Latest_AltServer_Version
    # AltStore
    if not os.path.exists(ALTSTORE_PATH):
        print(f"Downloading AltStore ipa {Latest_AltStore_Version}")
        DebugPrint(AltStore_URL)
        response = requests.get(AltStore_URL)
        open(ALTSTORE_PATH, "wb").write(response.content)
        current_version_json["AltStore"] = Latest_AltStore_Version
    # Netmuxd
    if not os.path.exists(NETMUXD_PATH) and NETMUXD_IS_AVAILABLE:
        print(f"Downloading netmuxd {Latest_Netmuxd_Version}")
        DebugPrint(Netmuxd_URL)
        response = requests.get(Netmuxd_URL)
        open(NETMUXD_PATH, "wb").write(response.content)
        current_version_json["Netmuxd"] = Latest_Netmuxd_Version
    # Anisette-Server
    if not os.path.exists(ANISETTE_SERVER_PATH):
        print(f"Downloading anisette-server {Latest_Anisette_Server_Version}")
        DebugPrint(Anisette_Server_URL)
        response = requests.get(Anisette_Server_URL)
        open(ANISETTE_SERVER_PATH, "wb").write(response.content)
        current_version_json["Anisette-Server"] = Latest_Anisette_Server_Version

    # Write updated json data into version.json
    with open(VERSION_JSON_PATH, "w") as outfile:
        json.dump(current_version_json, outfile)

    if not os.access(ALTSERVER_PATH, os.X_OK):
        print("Setting AltServer exec permission")
        os.chmod(ALTSERVER_PATH, 0o755)
    if os.path.exists(NETMUXD_PATH) and not os.access(NETMUXD_PATH, os.X_OK):
        print("Setting netmuxd exec permission")
        os.chmod(NETMUXD_PATH, 0o755)
    if not os.access(ANISETTE_SERVER_PATH, os.X_OK):
        print("Setting anisette-server permission")
        os.chmod(ANISETTE_SERVER_PATH, 0o755)

    DebugPrint(subprocess.getoutput(f"ls -al {RESOURCE_DIRECTORY}"))


def CheckNetworkConnection() -> bool:
    try:
        requests.get('http://google.com')
        return True
    except:
        return False


def CheckUpdate() -> bool:
    if not Version_Fetched:
        FetchVersion()
    with open(VERSION_JSON_PATH, 'r') as openfile:
        json_data = json.load(openfile)

    Current_Script_Version = json_data["Script"]
    Current_AltServer_Version = json_data["AltServer"]
    Current_AltStore_Version = json_data["AltStore"]
    Current_Netmuxd_Version = json_data["Netmuxd"]
    Current_Anisette_Server_Version = json_data["Anisette-Server"]

    global Script_Is_Updatable, AltServer_Is_Updatable, AltStore_Is_Updatable, Nermuxd_Is_Updatable, Anisette_Server_Is_Updatable
    # script
    if Latest_Script_Version != Current_Script_Version:
        Script_Is_Updatable = True
        print(
            f"Script is updatable , current ver : {Current_Script_Version} , latest ver : {Latest_Script_Version}")
    # altserver
    if Latest_AltServer_Version != Current_AltServer_Version:
        AltServer_Is_Updatable = True
        print(
            f"AltServer is updatable , current ver : {Current_AltServer_Version} , latest ver : {Latest_AltServer_Version}")
    # altstore
    if Latest_AltStore_Version != Current_AltStore_Version:
        AltStore_Is_Updatable = True
        print(
            f"AltStrore is updatable , current ver : {Current_AltStore_Version} , latest ver : {Latest_AltStore_Version}")
    # netmuxd
    if Latest_Netmuxd_Version != Current_Netmuxd_Version:
        Nermuxd_Is_Updatable = True
        print(
            f"Netmuxd is updatable , current ver : {Current_Netmuxd_Version} , latest ver : {Latest_Netmuxd_Version}")
    # anisette server
    if Latest_Anisette_Server_Version != Current_Anisette_Server_Version:
        Anisette_Server_Is_Updatable = True
        print(
            f"Anisette-Server is updatable , current ver : {Current_Anisette_Server_Version} , latest ver : {Latest_Anisette_Server_Version}")

    return AltServer_Is_Updatable or AltStore_Is_Updatable or Nermuxd_Is_Updatable or Anisette_Server_Is_Updatable or Script_Is_Updatable


def RemoveOutdatedResource():
    if AltServer_Is_Updatable:
        os.remove(ALTSERVER_PATH)
    if AltStore_Is_Updatable:
        os.remove(ALTSTORE_PATH)
    if Anisette_Server_Is_Updatable:
        os.remove(ANISETTE_SERVER_PATH)
    if Nermuxd_Is_Updatable:
        os.remove(NETMUXD_PATH)


def Update():
    if CheckUpdate():
        answer = getAnswer("Update available, Update now ? (y/n) : ").lower()
        if answer == 'y':
            # Remove outdated resource
            print("Removing outdated resource ...")
            RemoveOutdatedResource()
            # Update script
            if Script_Is_Updatable:
                print(
                    f"Downloading the lastest script [{Latest_Script_Version}] ...")
                response = requests.get(
                    "https://raw.githubusercontent.com/powenn/AltServer-Linux-PyScript/rewrite/main.py")
                open(SCRIPT_PATH, "wb").write(response.content)
                current_version_json["Script"] = Latest_Script_Version
                with open(VERSION_JSON_PATH, "w") as outfile:
                    json.dump(current_version_json, outfile)

            print("\n\nUpdate done\nYou can find update log in https://github.com/powenn/AltServer-Linux-PyScript/releases\nScript requires restart to apply updates\nUse `e` option to exit the script\n\n")
    else:
        print("All resources and script are up to dated :)")


class AnisetteServer:
    def __init__(self, host=ANISETTE_HOST, port=ANISETTE_PORT):
        self.host = host
        self.port = port
        os.environ["ALTSERVER_ANISETTE_SERVER"] = f"http://{host}:{port}"
        DebugPrint(os.environ["ALTSERVER_ANISETTE_SERVER"])
        DebugPrint(f"{ANISETTE_SERVER_PATH} -n {host} -p {port}")
        self.server = subprocess.Popen(
            f"{ANISETTE_SERVER_PATH} -n {host} -p {port}", shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)

    def kill(self):
        print(subprocess.getoutput("killall anisette-server"))


class AltServerDaemon:
    def __init__(self):
        self.start()

    def start(self):
        self.altserver = subprocess.Popen(
            ALTSERVER_PATH, shell=True)  # ,env=os.environ)

    def kill(self):
        print(subprocess.getoutput("killall AltServer"))

    def restart(self):
        self.kill()
        self.start()


class Netmuxd:
    def __init__(self):
        if Netmuxd_is_on:
            if subprocess.getoutput("echo $(pidof usbmuxd)") != "":
                print(subprocess.getoutput("sudo kill -9 $(pidof usbmuxd)"))
            self.start()

    def start(self):
        self.netmuxd = subprocess.Popen(f"sudo -b {NETMUXD_PATH}", shell=True)

    def kill(self):
        print(subprocess.getoutput("sudo killall netmuxd"))

    def switchWiFi(self):
        global Netmuxd_is_on
        Netmuxd_is_on = True
        DebugPrint(f"NETMUXD : {Netmuxd_is_on}")
        print(subprocess.getoutput("sudo kill -9 $(pidof usbmuxd)"))
        self.kill()
        self.start()

    def switchTether(self):
        global Netmuxd_is_on
        Netmuxd_is_on = False
        DebugPrint(f"NETMUXD : {Netmuxd_is_on}")
        print(subprocess.getoutput("sudo usbmuxd"))
        self.kill()


def getSUDO():
    output = ""
    password = ""
    if os.geteuid() != 0:
        while output[:-1] != "0000":
            password = getpass.getpass("Enter sudo password : ")
            p = subprocess.Popen("sudo -S echo '0000'", stdin=subprocess.PIPE,
                             stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True, shell=True)
            prompt = p.communicate(password + '\n')
            output = prompt[0]
        DebugPrint(output[:-1])


class iDevice:
    def __init__(self, name, UDID):
        self.name = name
        self.UDID = UDID


class DeviceManager:
    def __init__(self, devices=[]):
        self.devices = devices

    def getDevices(self) -> list[iDevice]:
        DebugPrint(f"NETMUXD : {Netmuxd_is_on}")
        self.devices = []
        udids = subprocess.getoutput("idevice_id -n").split(
            '\n') if Netmuxd_is_on else subprocess.getoutput("idevice_id -l").split('\n')
        DebugPrint(udids)
        if udids == ['']:
            print("No devices found")
        else:
            for udid in udids:
                name = subprocess.getoutput(
                    f"ideviceinfo -n -u {udid} -k DeviceName") if Netmuxd_is_on else subprocess.getoutput(f"ideviceinfo -u {udid} -k DeviceName")
                d = iDevice(name=name, UDID=udid)
                self.devices.append(d)
        return self.devices


class InstallationManager:
    def __init__(self):
        pass

    def selectDevice(self, devices: list[iDevice]):
        for i in range(len(devices)):
            print(f"[{i}] : {devices[i].name} , {devices[i].UDID}")
        try:
            index = int(
                getAnswer("Enter the index of the device for installation : "))
            self.selectedDevice = devices[index]
        except:
            print("Invalid index")
            self.selectedDevice = None

    def getAccount(self):
        ac = getAnswer("Enter your Apple ID : ")
        self.account = ac

    def getPassword(self):
        pd = getpass.getpass("Enter password of the Apple ID : ")
        self.password = pd

    def selectFile(self):
        answer = getAnswer(
            "Do you want to install AltStore ? (y/n) [n for select your own iPA] : ").lower()
        if answer == 'n':
            filePath = getAnswer("Enter the absolute path of the file : ")
            if filePath != "":
                self.filePath = filePath
            else:
                self.filePath = None
                print("No file path entered")
        else:
            self.filePath = ALTSTORE_PATH

    def run(self):
        subprocess.run(
            f"{ALTSERVER_PATH} -u {self.selectedDevice.UDID} -a '{self.account}' -p '{self.password}' {self.filePath}", shell=True)

    def getInfo(self) -> str:
        return [self.selectedDevice.name, self.account, self.password, self.filePath]


def main():
    if CheckNetworkConnection() == False:
        print("Please connect to network and re-run the script")
        input("Press enter to exit : ")
        exit(-1)
    CheckResource()
    CheckUpdate()
    if NETMUXD_IS_AVAILABLE:
        getSUDO()
    anisetteserver = AnisetteServer()
    netmuxd = Netmuxd()
    altserverdaemon = AltServerDaemon()
    device_manager = DeviceManager()
    installaion_manager = InstallationManager()
    print(HELP_MSG)
    while True:
        option = getAnswer("Enter OPTION to continue : ").lower()

        if option == 'i':
            devices = device_manager.getDevices()
            if len(devices) == 0:
                continue
            installaion_manager.selectDevice(devices=devices)
            if installaion_manager.selectedDevice == None:
                continue
            installaion_manager.getAccount()
            installaion_manager.getPassword()
            installaion_manager.selectFile()
            if installaion_manager.filePath == None:
                continue
            DebugPrint(installaion_manager.getInfo())
            installaion_manager.run()

        elif option == 'w':
            if NETMUXD_IS_AVAILABLE:
                if not Netmuxd_is_on:
                    netmuxd.switchWiFi()
                    altserverdaemon.restart()
            else:
                print(f"Netmuxd is not support arch : {ARCH}")

        elif option == 't':
            if Netmuxd_is_on:
                netmuxd.switchTether()
                altserverdaemon.restart()

        elif option == 'e':
            altserverdaemon.kill()
            anisetteserver.kill()
            if Netmuxd_is_on:
                netmuxd.kill()
            break

        elif option == 'h':
            print(HELP_MSG)

        elif option == 'p':
            devices = device_manager.getDevices()
            for d in devices:
                print(f"{d.name} , {d.UDID}")

        elif option == 'u':
            Update()

        else:
            print("Invalid option")


HELP_MSG = """
#####################################
#  Welcome to the AltServer script  #
#####################################

ScriptUsage: [OPTION]

OPTIONS
    i, --Install AltStore or ipa files
        Install AltStore or ipa files to your device
    w, --Switch to wifi Daemode mode (Default using it after launch)
        Switch and restart to wifi Daemode mode to refresh apps or AltStore
    t, --Switch to usb tethered Daemode mode
        Switch and restart to usb tethered Daemode mode to refresh apps or AltStore
    e, --Exit
        Exit script
    h, --Help
        Show this message
    p, --Pair
        Show paired devices
    u, --Update
        Update this script
        
For more information: 
https://github.com/powenn/AltServer-Linux-PyScript

"""

if __name__ == '__main__':
    DebugPrint("Script Start")
    DebugPrint(
        f"RUNNING AT {CURRENT_DIRECTORY} , RESOURCE_DIR : {RESOURCE_DIRECTORY}")
    DebugPrint(f"ARCH : {ARCH} , NETMUXD_AVAILABLE : {NETMUXD_IS_AVAILABLE}")
    main()
