import os
import sys
import plistlib
from paramiko.rsakey import RSAKey
from paramiko.ecdsakey import ECDSAKey
from paramiko.ed25519key import Ed25519Key
from paramiko import SSHException
from PyQt5.QtWidgets import QFileDialog
from PyQt5 import QtCore
import subprocess
import keyring
from .models import WifiSettingModel


class RestaticKeyring(keyring.backend.KeyringBackend):
    """Fallback keyring service."""

    @classmethod
    def priority(cls):
        return 5

    def set_password(self, service, repo_url, password):
        from .models import RepoPassword

        keyring_entry, created = RepoPassword.get_or_create(
            url=repo_url, defaults={"password": password}
        )
        keyring_entry.password = password
        keyring_entry.save()

    def get_password(self, service, repo_url):
        from .models import RepoPassword

        try:
            keyring_entry = RepoPassword.get(url=repo_url)
            return keyring_entry.password
        except Exception:
            return None

    def delete_password(self, service, repo_url):
        pass


"""Select keyring/Workaround for pyinstaller+keyring issue."""
if sys.platform == "darwin":
    from keyring.backends import OS_X

    keyring.set_keyring(OS_X.Keyring())
elif sys.platform == "win32":
    from keyring.backends import Windows

    keyring.set_keyring(Windows.WinVaultKeyring())
elif sys.platform == "linux":
    from keyring.backends import SecretService

    try:
        SecretService.Keyring.priority()  # Test if keyring works.
        keyring.set_keyring(SecretService.Keyring())
    except Exception:
        keyring.set_keyring(RestaticKeyring())
else:  # Fall back to saving password to database.
    keyring.set_keyring(RestaticKeyring())


def choose_folder_dialog(parent, title):
    options = QFileDialog.Options()
    options |= QFileDialog.ShowDirsOnly
    dialog = QFileDialog(parent, title, os.path.expanduser("~"), options=options)
    dialog.setFileMode(QFileDialog.Directory)
    dialog.setParent(parent, QtCore.Qt.Sheet)
    return dialog


def get_private_keys():
    """Find SSH keys in standard folder."""
    key_formats = [RSAKey, ECDSAKey, Ed25519Key]

    ssh_folder = os.path.expanduser("~/.ssh")

    available_private_keys = []
    if os.path.isdir(ssh_folder):
        for key in os.listdir(ssh_folder):
            for key_format in key_formats:
                try:
                    parsed_key = key_format.from_private_key_file(
                        os.path.join(ssh_folder, key)
                    )
                    key_details = {
                        "filename": key,
                        "format": parsed_key.get_name(),
                        "bits": parsed_key.get_bits(),
                        "fingerprint": parsed_key.get_fingerprint().hex(),
                    }
                    available_private_keys.append(key_details)
                except (SSHException, UnicodeDecodeError, IsADirectoryError):
                    continue

    return available_private_keys


def pretty_bytes(size):
    """from https://stackoverflow.com/questions/12523586/
            python-format-size-application-converting-b-to-kb-mb-gb-tb/37423778"""
    if type(size) != int:
        return ""
    power = 1000  # GiB is base 2**10, GB is base 10**3.
    n = 0
    Dic_powerN = {0: "", 1: "K", 2: "M", 3: "G", 4: "T"}
    while size > power:
        size /= power
        n += 1
    return f"{round(size, 1)} {Dic_powerN[n]}B"


def get_asset(path):
    if getattr(sys, "frozen", False):
        # we are running in a bundle
        bundle_dir = os.path.join(sys._MEIPASS, "assets")
    else:
        # we are running in a normal Python environment
        bundle_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets")
    return os.path.join(bundle_dir, path)


def get_sorted_wifis(profile):
    """Get SSIDs from OS and merge with settings in DB."""

    if sys.platform == "darwin":
        plist_path = "/Library/Preferences/SystemConfiguration/com.apple.airport.preferences.plist"
        plist_file = open(plist_path, "rb")
        wifis = plistlib.load(plist_file)["KnownNetworks"]
        if wifis:
            for wifi in wifis.values():
                timestamp = wifi.get("LastConnected", None)
                ssid = wifi["SSIDString"]
                db_wifi, created = WifiSettingModel.get_or_create(
                    ssid=ssid,
                    profile=profile.id,
                    defaults={"last_connected": timestamp, "allowed": True},
                )

                # update last connected time
                if not created and db_wifi.last_connected != timestamp:
                    db_wifi.last_connected = timestamp
                    db_wifi.save()

        # remove Wifis that were deleted in the system.
        deleted_wifis = WifiSettingModel.select().where(
            WifiSettingModel.ssid.not_in([w["SSIDString"] for w in wifis.values()])
        )
        for wifi in deleted_wifis:
            wifi.delete_instance()

    return (
        WifiSettingModel.select()
        .where(WifiSettingModel.profile == profile.id)
        .order_by(-WifiSettingModel.last_connected)
    )


def get_current_wifi():
    """
    Get current SSID or None if Wifi is off.

    From https://gist.github.com/keithweaver/00edf356e8194b89ed8d3b7bbead000c
    """

    if sys.platform == "darwin":
        cmd = [
            "/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport",
            "-I",
        ]
        process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
        out, err = process.communicate()
        process.wait()
        for line in out.decode("utf-8").split("\n"):
            split_line = line.strip().split(":")
            if split_line[0] == "SSID":
                return split_line[1].strip()