#!/usr/local/bin/python3.11
"""
mkpasswd clone of the command present in the whois package available in Debian
"""

import argparse
import getpass
import os
import sys

import passlib.hash


IGNORED = set("htdigest msdcc msdcc2 oracle10 postgres_md5 roundup_plaintext unix_disabled unix_fallback".split())
METHODS = [m for m in dir(passlib.hash) if not m.startswith("_") and m not in IGNORED]


def print_methods():
    """
    Print available methods
    """
    print("Available methods:")
    print("\n".join(METHODS))


def method(string: str) -> str:
    """
    Check method option
    """
    string = string.lower().replace("-", "")
    if string == "help":
        print_methods()
        sys.exit(0)
    if string in METHODS:
        return string
    if f"{string}_crypt" in METHODS:
        return f"{string}_crypt"
    raise argparse.ArgumentTypeError(f"Invalid method: {string}")


def get_pass(opts) -> str:
    """
    Get password
    """
    if opts.password:
        return opts.password
    if opts.stdin:
        return sys.stdin.readline().rstrip()
    if opts.password_fd is not None:
        try:
            return os.fdopen(opts.password_fd).readline().rstrip()
        except ValueError as err:
            sys.exit(f"{err}")
    passwd = getpass.getpass("Password: ")
    passwd2 = getpass.getpass("Password (again): ")
    if passwd != passwd2:
        sys.exit("ERROR: Passwords do not match")
    return passwd


def parse_opts():
    """
    Parse opts
    """
    parser = argparse.ArgumentParser()
    parser.add_argument('-m', '--method', default="des_crypt", type=method,
                        help="Compute the password using the TYPE method")
    parser.add_argument('-R', '--rounds', default="",
                        help="Use the specified number of rounds")
    parser.add_argument('-S', '--salt', default="",
                        help="Use the string as salt")
    parser.add_argument('-P', '--password-fd', type=int,
                        help="Read the password from the specified file descriptor")
    parser.add_argument('-s', '--stdin', action='store_true',
                        help="like --password-fd=0")
    parser.add_argument('password', nargs='?')
    return parser.parse_args()


def main():
    """
    Main function
    """
    opts = parse_opts()
    crypt = getattr(passlib.hash, opts.method)
    args = {}
    if opts.rounds:
        try:
            if not crypt.min_rounds <= int(opts.rounds) <= crypt.max_rounds:
                sys.exit(f"Invalid number of rounds: {opts.rounds} should be between {crypt.min_rounds} and {crypt.max_rounds}")
        except AttributeError:
            sys.exit(f"Method does not use rounds: {opts.method}")
        except ValueError:
            sys.exit(f"Invalid number '{opts.rounds}'.")
        args = {"rounds": opts.rounds}
    if opts.salt:
        if not set(opts.salt) <= set(crypt.salt_chars):
            sys.exit(f"Invalid characters in salt for {opts.method}. Valid set: {crypt.salt_chars}")
        try:
            if not crypt.min_salt_size <= len(opts.salt) <= crypt.max_salt_size:
                sys.exit(f"Wrong salt length: {len(opts.salt)} should be between {crypt.min_salt_size} and {crypt.max_salt_size}")
        except AttributeError:
            sys.exit(f"Method does not use salt: {opts.method}")
        args.update({"salt": opts.salt})
    try:
        print(crypt.using(**args).hash(secret=get_pass(opts)))
    except ValueError as exc:
        sys.exit(str(exc))


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        sys.exit(1)
