import hashlib
import os
import stat
from pathlib import Path
from typing import Generator, List, Dict, Any, Tuple, Callable


def list_directory(
    pg_fn: Callable[[], Generator[Path, None, None]], dirs_only: bool = False, image_buckets: Dict[str, int] = None
) -> List[Dict[str, Any]]:
    """
    Create entries of the items in a directory.
    NOTE: if this is changed ls_entry_size() must be modified accordingly!
    """
    if image_buckets is None:
        image_buckets = {}
    es = []
    for p in pg_fn():
        e = {"name": (name := p.name)}
        try:
            s = p.stat()
        except OSError:
            e["unresolved"] = True
        else:
            e["mtime"] = s.st_mtime
            if stat.S_ISDIR(s.st_mode):
                pass
            elif dirs_only:
                continue
            else:
                e["size"] = s.st_size
                if img_bucket := image_buckets.get(name):
                    e["imgb"] = img_bucket
        es.append(e)
    return es


def ls_entry_size(item: dict) -> int:
    """
    To be used with items from list_directory() with _approximate_part_slicing().
    Each field is approximated: (+1 for root)
        name: 6 + actual encoded length of the name
        mtime: 15
        size: 5 + 1, 2, 4 or 8 depending on size (we'll simplify and count as 4)
        imgb: 6 if present
    NOTE: extra length indicating the size of name is not accounted for.
    """
    return 31 + len(item["name"].encode("utf-8")) + (6 if "imgb" in item else 0)


def file_stat(p: Path) -> os.stat:
    return p.stat()


def read_file_range(p: Path, start: int, n: int) -> bytes:
    with open(p, "rb") as f:
        f.seek(start)
        return f.read(n)


def fingerprint_file_range(p: Path, start: int, n: int, times: int, salt: bytes) -> Tuple[bytes, int]:
    # TODO: use HMAC, CMAC or Poly1305
    h = hashlib.sha512(salt)
    c = 0
    with open(p, "rb") as f:
        f.seek(start)
        for _ in range(times):
            b = f.read(n)
            c += len(b)
            h.update(b)
    return h.digest()[:32], c


def fingerprint_parts_data(parts: List[bytes], salt: bytes) -> Tuple[bytes, int]:
    # TODO: use HMAC, CMAC or Poly1305
    h = hashlib.sha512(salt)
    c = 0
    for data in parts:
        h.update(data)
        c += len(data)
    return h.digest()[:32], c
