import argparse
import hashlib
import sys
import time
from pathlib import Path


def hash_password(password: str, algo: str = "md5") -> str:
    """Return the hex digest of *password* using the specified algorithm.

    Currently supports "md5" and "sha1". The function is written in a way
    that makes adding additional algorithms trivial.
    """
    algo = algo.lower()
    if algo == "md5":
        return hashlib.md5(password.encode("utf-8")).hexdigest()
    elif algo == "sha1":
        return hashlib.sha1(password.encode("utf-8")).hexdigest()
    else:
        raise ValueError(f"Unsupported hash algorithm: {algo}")


def process_wordlist(wordlist_path: str, target_hash: str, algo: str):
    """Iterate over *wordlist_path*, hashing each line and comparing.

    Returns a tuple ``(found, password, line_no, processed, elapsed, speed)``.
    ``found`` is a bool indicating whether the target hash was matched.
    If ``found`` is ``True`` the other returned values provide details of the
    match.  If ``found`` is ``False`` the match-related fields are ``None``.
    """
    processed = 0
    start_time = time.perf_counter()
    try:
        with open(wordlist_path, "r", encoding="utf-8", errors="ignore") as f:
            for line_no, raw_line in enumerate(f, 1):
                password = raw_line.rstrip("\n\r")
                if not password:
                    continue  # skip empty lines
                try:
                    h = hash_password(password, algo=algo)
                except ValueError as exc:
                    print(f"[!] {exc}", file=sys.stderr)
                    sys.exit(1)
                processed += 1
                if h.lower() == target_hash.lower():
                    elapsed = time.perf_counter() - start_time
                    speed = processed / elapsed if elapsed > 0 else 0
                    return True, password, line_no, processed, elapsed, speed
    except FileNotFoundError:
        print(f"[!] Wordlist file not found: {wordlist_path}", file=sys.stderr)
        sys.exit(1)
    elapsed = time.perf_counter() - start_time
    speed = processed / elapsed if elapsed > 0 else 0
    return False, None, None, processed, elapsed, speed


def parse_arguments():
    parser = argparse.ArgumentParser(description="Simple hashcat clone in pure Python.")
    parser.add_argument(
        "hash",
        help="The hash value to crack (hex string)",
    )
    parser.add_argument(
        "dictionary",
        help="Path to a file containing candidate passwords (one per line)",
    )
    parser.add_argument(
        "-m",
        "--mode",
        default="md5",
        help="Hash algorithm to use (default: md5). Supports 'md5' and 'sha1'.",
    )
    return parser.parse_args()


def load_banner() -> str:
    """Read the banner from ``banner.txt`` located next to this script."""
    banner_path = Path(__file__).with_name("banner.txt")
    try:
        return banner_path.read_text(encoding="utf-8")
    except FileNotFoundError:
        return ""


def main():
    print(load_banner())

    args = parse_arguments()

    found, password, line_no, processed, elapsed, speed = process_wordlist(
        args.dictionary, args.hash, args.mode
    )
    if found:
        print(f"[+] Found password on line {line_no}: {password}")
        print(f"[+] Total hashed: {processed}")
        print(f"[+] Runtime: {elapsed:.2f} s")
        print(f"[+] Hashing speed: {speed:.2f} hashes/sec")
        sys.exit(0)
    else:
        print("[-] Password not found in the wordlist.")
        print(f"[-] Total hashed: {processed}")
        print(f"[-] Runtime: {elapsed:.2f} s")
        print(f"[-] Hashing speed: {speed:.2f} hashes/sec")
        sys.exit(1)


if __name__ == "__main__":
    main()
