import logging
import os.path
from argparse import ArgumentParser, Namespace
from pathlib import Path
from typing import Optional, List

from blake3 import blake3
from tqdm import tqdm

from bagsy.bagsyhub import bagSyHub
from bagsy.database import Database
from bagsy.logger import setup_logging
from bagsy.parser import parse_and_exec

logger = logging.getLogger(__name__)


def compute_checksum(filepath: Path, chunk_size: int = 512 * 1024) -> str:
    b3 = blake3()
    with open(filepath, 'rb') as f:
        with tqdm(total=os.path.getsize(filepath), unit='B', unit_scale=True,
                  desc='Computing checksum', leave=False, mininterval=1, miniters=20) as pbar:
            while chunk := f.read(chunk_size):
                b3.update(chunk)
                pbar.update(len(chunk))
    return b3.hexdigest()


# === command line interface

def setup_parser(parser: ArgumentParser) -> None:
    parser.add_argument("token_id", type=int, help="The id of the token to use. See `bagsy login --list`")
    parser.add_argument("bag_path", type=Path, help="Path to rosbag.")
    parser.add_argument("name", type=str, help="The name of the bag.")
    parser.add_argument("--description", "-d", type=str, help="The description of the bag.")
    parser.add_argument("--skip-integrity", default=False, action="store_true",
                        help="Do not check bag integrity using sha1.")


def exec_cmd(db: Database, args: Namespace, _parser: ArgumentParser):
    bag_path = args.bag_path
    if not os.path.exists(bag_path):
        logger.error(f"No file '{bag_path}'")
        return None
    if len(args.name) == 0:
        logger.error(f"The name can't be empty")
        return None
    if args.description is None or len(args.description) == 0:
        logger.warning(f"Adding a description is recommended")

    config = db.get_token(args.token_id)
    if config is None:
        logger.error("Unknown token config")
        return None
    hub = bagSyHub(*config)

    checksum = compute_checksum(bag_path) if not args.skip_integrity else None

    bag = db.get_push_queue_element(str(bag_path), checksum)
    if bag is None:
        data = {"name": args.name,
                "size": os.path.getsize(bag_path),
                "checksum": checksum,
                "description": args.description}

        r = hub.post("/rosbag/register", json=data)
        if r is None:
            return None
        uid = r["uid"]
        logger.debug(f"Fetch uid ({uid}) successfully.")
        bag = db.add_to_push_queue(uid, str(bag_path), checksum)
        if bag is None:
            logger.warning(f"Could not save the associated uid of this bag.")
    else:
        uid = bag.server_uid
        if bag.pushed:
            logger.info(f"This bag was already pushed... Checking status on the server.")
            logger.warning(f"This is not yet implemented.")  # TODO check status
            return None
        elif bag.server_checked:
            logger.info(f"This bag was already pushed and the server validated the upload.")
            return None
        logger.info(f"This bag was previously registered, restarting upload.")
    with open(args.bag_path, 'rb') as f:
        r = hub.post(f"/rosbag/upload/{uid}", data=f, headers={'Content-Type': 'application/octet-stream'})
        if r is None:
            return None
    logger.info(f"Bag was successfully sent!")
    if bag is None or not db.update_push_queue_element(bag):
        logger.warning(f"Could not save it was pushed.")
    return None


def main(cli_args: Optional[List[str]] = None):
    setup_logging()

    parse_and_exec("bagsy.push",
                   "bag-push",
                   "A python cli tool to push to a bagsyhub instance.",
                   cli_args)


if __name__ == "__main__":
    main()
