import logging
import os
import re
import socket
import subprocess
from random import randint
from time import sleep

import adbutils
from rich.logging import RichHandler
from rich.console import Console
from rich.progress import (
    BarColumn,
    DownloadColumn,
    Progress,
    TextColumn,
    TimeRemainingColumn,
    TransferSpeedColumn,
)

from . import Files


console = Console(log_path=False)


class TimeRemainingColumnCustom(TimeRemainingColumn):
    max_refresh = 1


FORMAT = "%(message)s"
logging.basicConfig(
    level="INFO", format=FORMAT, datefmt="[%X]", handlers=[RichHandler()]
)

logger = logging.getLogger("Deployer")


class DeviceNotFound(Exception):
    pass


def get_progress() -> Progress:
    return Progress(
        TextColumn("[bold blue]{task.description}", justify="right"),
        BarColumn(bar_width=None),
        "[progress.percentage]{task.percentage:>3.1f}%",
        "•",
        DownloadColumn(),
        "•",
        TransferSpeedColumn(),
        "•",
        TimeRemainingColumnCustom(),
    )


def fastboot_run(command: [str], serial: str = None) -> str:

    try:
        if not serial:
            cmd = f"fastboot {' '.join(command)}"
        else:
            cmd = f"fastboot -s {serial} {' '.join(command)}"
        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
    except FileNotFoundError:
        console.log("Fastboot binary not found")
        console.log("Exiting")
        exit(1)
    else:
        stdout, stderr = proc.communicate()
        proc.wait()
        logger.debug(f"fb-cmd: {cmd}")
        logger.debug(f"fb-out: {stdout}")
        logger.debug(f"fb-err: {stderr}")
        return (stdout if stdout else stderr).decode()


def list_fb_devices() -> [str]:
    return [x.split("\t ")[0] for x in fastboot_run(["devices"]).split("\n")[:-1]]


def check_device(serial: str) -> bool:
    return "nabu" in fastboot_run(["getvar", "product"], serial=serial)


def check_parts(serial: str) -> bool:
    linux_response = fastboot_run(["getvar", "partition-type:linux"], serial=serial)
    esp_response = fastboot_run(["getvar", "partition-type:esp"], serial=serial)
    return linux_response != "FAILED" and esp_response != "FAILED"


def reboot_fb_device(serial: str) -> None:
    fastboot_run(["reboot"], serial=serial)


def check_port(tcp_port: int) -> bool:
    s = socket.socket()
    try:
        s.bind(("127.0.0.1", tcp_port))
    except OSError as e:
        if e.errno == 98:
            return False
    return True


def get_port() -> int:
    while True:
        tcp_port = randint(10000, 60000)
        if check_port(tcp_port):
            return tcp_port


def boot_ofox(serial: str) -> None:
    Files.OrangeFox.get()
    ofox = Files.OrangeFox.filepath
    with console.status("[cyan]Booting", spinner="line", spinner_style="white"):
        fastboot_run(["boot", ofox])


def flash_boot(serial: str, boot_data: bytes) -> None:
    proper_flash(serial, "boot", boot_data)


def proper_flash(serial: str, part: str, data: bytes) -> None:
    with open("image.img", "wb") as file:
        file.write(data)

    with console.status(f"[cyan]Flashing {part}", spinner="line", spinner_style="white"):
        fastboot_run(["flash", part, "image.img"], serial=serial)

    os.remove("image.img")


def restore_parts(serial: str) -> None:
    gpt_both = Files.GPT_Both0.get()
    userdata = Files.UserData_Empty.get()
    proper_flash(serial, "partition:0", gpt_both)
    proper_flash(serial, "userdata", userdata)


def clean_device(serial: str) -> None:
    fastboot_run(["erase", "linux"], serial=serial)
    fastboot_run(["erase", "esp"], serial=serial)


def repartition(serial: str, size: int, percents=False) -> None:
    device = adbutils.adb.device(serial)
    block_size = device.shell("blockdev --getsize64 /dev/block/sda")
    if re.match(r"^125[0-9]{9}$", block_size):
        maxsize = 126
    elif re.match(r"^253[0-9]{9}$", block_size):
        maxsize = 254
    else:
        logger.error("Weird block size. Is it nabu?")
        exit(4)
    linux_max = maxsize - 12
    if percents:
        size = round(linux_max / 100 * size, 2)

    if size > linux_max:
        raise ValueError("Too big partition")
    userdata_end = maxsize - 1 - size
    linux_end = userdata_end + size
    cmds = [
        "sgdisk --resize-table 64 /dev/block/sda",
        f"parted -s /dev/block/sda rm 31",
        f"parted -s /dev/block/sda mkpart userdata ext4 10.9GB {userdata_end}GB",
        f"parted -s /dev/block/sda mkpart linux ext4 {userdata_end}GB {linux_end}GB",
        f"parted -s /dev/block/sda mkpart esp fat32 {linux_end}GB {maxsize}GB",
        f"parted -s /dev/block/sda set 33 esp on"
    ]
    for cmd in cmds:
        device.shell(cmd)
    sleep(3)


def wait_for_bootloader(serial: str) -> None:
    fastboot_run(["getvar", "product"], serial=serial)