"""
Hashing module for easy-dataset-share.

This module provides functionality to hash files and directories while excluding
canary files and other metadata files that shouldn't affect the dataset hash.

The module is designed to work with the CLI commands to provide consistent
hashing before and after canary operations, ensuring that the dataset integrity
can be verified independently of canary files.
"""

import hashlib
from pathlib import Path
from typing import Dict, List

import click


def hash_file(file_path: Path) -> str | None:
    """
    Calculate SHA256 hash of a file.

    Returns:
        SHA256 hex digest of the file content, or None if the file cannot be read
    """
    try:
        hash_sha256 = hashlib.sha256()
        with open(file_path, "rb") as f:
            for chunk in iter(lambda: f.read(4096), b""):
                hash_sha256.update(chunk)
        return hash_sha256.hexdigest()
    except (OSError, IOError, UnicodeDecodeError):
        # Skip files that can't be read (permissions, encoding issues, etc.)
        return None


def hash_directory(
    directory: str,
    exclude_patterns: List[str] | None = None,
    verbose: bool = False,
) -> Dict:
    """
    Hash all files in a directory, excluding specified patterns.

    """
    if exclude_patterns is None:
        exclude_patterns = ["dataset_entry_*.jsonl", "robots.txt", "tos.txt"]

    directory_path = Path(directory)
    if not directory_path.exists() or not directory_path.is_dir():
        raise ValueError(f"Directory does not exist: {directory}")

    file_hashes = {}
    total_files = 0
    excluded_files = 0

    for file_path in directory_path.rglob("*"):
        if file_path.is_file():
            # Check if file should be excluded
            should_exclude = False
            for pattern in exclude_patterns:
                if file_path.match(pattern):
                    should_exclude = True
                    break

            if should_exclude:
                excluded_files += 1
                if verbose:
                    click.echo(f"   Excluding: {file_path.relative_to(directory_path)}")
                continue

            try:
                file_hash = hash_file(file_path)
                if file_hash is None:
                    # File couldn't be read, skip it
                    if verbose:
                        click.echo(f"   Skipping unreadable file: {file_path.relative_to(directory_path)}")
                    continue

                relative_path = str(file_path.relative_to(directory_path))
                file_hashes[relative_path] = file_hash
                total_files += 1
                if verbose:
                    click.echo(f"   {relative_path}: {file_hash}")
            except Exception as e:
                if verbose:
                    click.echo(f"   Error hashing {file_path}: {e}", err=True)

    # Calculate overall directory hash
    all_hashes = sorted(file_hashes.items())
    combined_hash = hashlib.sha256()
    for file_path, file_hash in all_hashes:
        combined_hash.update(f"{file_path}:{file_hash}\n".encode())
    directory_hash = combined_hash.hexdigest()

    return {
        "directory_hash": directory_hash,
        "file_hashes": file_hashes,
        "total_files": total_files,
        "excluded_files": excluded_files,
    }
