import hashlib
from typing import *
from pathlib import Path
from enum import Enum

from typer import Argument, Option

from ..app import app


class InvalidHashAlgoException(Exception):
    def __init__(self, hash_name: str):
        super().__init__(f"{hash_name} is not supported")


class FileNotFoundExecption(Exception):
    def __init__(self, path: Path) -> None:
        super().__init__(f"{path.as_posix()} not found")


class HashAlgo(str, Enum):
    md5: str = "md5"
    sha1: str = "sha1"
    sha224: str = "sha224"
    sha256: str = "sha256"
    sha384: str = "sha384"

    def __call__(self):
        if self == HashAlgo.md5:
            return hashlib.md5()

        if self == HashAlgo.sha1:
            return hashlib.sha1()

        if self == HashAlgo.sha224:
            return hashlib.sha224()

        if self == HashAlgo.sha256:
            return hashlib.sha256()

        if self == HashAlgo.sha384:
            return hashlib.sha384()

        raise InvalidHashAlgoException(self.value)


@app.command(callback=print)
def hash_file(
    path: Path = Argument(..., help="File to hash"),
    algo: HashAlgo = Option(HashAlgo.md5.value, help="Hash generator"),
    num_block: int = Option(16, help="Number of block for each step"),
    length: int = Option(-1, help="Output length"),
):
    if not path.exists():
        raise FileNotFoundExecption(path)

    hash = algo()
    with open(path, "rb") as fin:
        while True:
            buf = fin.read(num_block * hash.block_size)
            if not buf:
                break
            hash.update(buf)
    hash = hash.hexdigest()

    if length > 0:
        hash = hash.encode()
        hash = hashlib.shake_256(hash).hexdigest(length=length // 2)

    return hash


@app.command(callback=print)
def hash_str(
    string: str = Argument(..., help="String to hash"),
    algo: HashAlgo = Option(HashAlgo.md5.value, help="Hash generator"),
    length: int = Option(-1, help="Output length"),
):
    hash = algo()
    hash.update(string.encode())
    hash = hash.hexdigest()

    if length > 0:
        hash = hash.encode()
        hash = hashlib.shake_256(hash).hexdigest(length=length // 2)

    return hash
