from bs4 import BeautifulSoup
import httpx, lxml # need lxml as bs4 parser
from pprint import pp
import pandas as pd
from typing import Set
import random
from io import StringIO


def fetch_names_table() -> pd.DataFrame:
    """Grab the list of most common names across the last century from the american social security agency SSA
    and parse it out as a pandas dataframe"""
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
        "Accept-Language": "en-US,en;q=0.9,de;q=0.8",
    }
    page = httpx.get('https://www.ssa.gov/OACT/babynames/decades/century.html', headers=headers)
    soup = BeautifulSoup(page.content, 'lxml')
    table = soup.find('table')
    with StringIO(str(table)) as f:
        df = pd.read_html(f)[0]
    return df

def make_names_set() -> Set[str]:
    """extract the male & female names from the SSA table and lowercase them into a single set (to be 100% unique, even if
    they should already be from the source material)"""
    my_names = fetch_names_table()
    return set([ n.lower() for n in [ n for n in my_names['Females']['Name'] ] + [ n for n in my_names['Males']['Name'] ] if ' ' not in n ])

def get_randomized_names_list() -> list[str]:
    """takes the unique (ordered) set of common names and returns a shuffled version as a list to be used for
    sequentially assigning randomized user names to containers"""
    names = list(make_names_set())
    random.shuffle(names)
    return(names)



def sha512_crypt(password: str, salt: bytes = None) -> str:
    """
    Produce a SHA‑512 crypt‑style hash (like ``crypt.crypt(..., METHOD_SHA512)``)
    using only ``hashlib``.

    The output format matches the traditional ``$6$salt$hash`` string.
    """
    # Generate a 16‑byte salt if none is provided
    if salt is None:
        salt = os.urandom(16)

    # Encode password and salt as bytes
    pwd_bytes = password.encode("utf-8")
    # ``crypt`` uses a base64 variant; ``base64`` with ``urlsafe_b64encode``
    # and stripping padding gives the same character set.
    salt_str = base64.urlsafe_b64encode(salt).rstrip(b"=").decode("ascii")

    # Perform the SHA‑512 hash
    hasher = hashlib.sha512()
    hasher.update(pwd_bytes + salt)
    digest = hasher.digest()

    # Encode the digest in the same base64 variant
    hash_str = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")

    # Return the full crypt‑style string
    return f"$6${salt_str}${hash_str}"

def create_password(username: str) -> str:
    """generate a crypted pw hash for this username"""
    return sha512_crypt(f'{username}123!')

def my_password(password) -> str:
    salt = os.urandom(8)
    pwd_hash = sha512_crypt.using(salt=salt, rounds=1000).hash(password)
    return pwd_hash

if __name__ == "__main__":
    import hashlib
    import os
    import base64
    import yaml
    from xkcd_passwords import generate_password
    from passlib.hash import sha512_crypt
    from pathlib import Path
    with Path('../names.yml').open('r') as yaml_file:
        names = yaml.safe_load(yaml_file)
    # names = get_randomized_names_list()
    # name_pw_map = { n: (sha512_crypt(pwd:= generate_password(separator="", capitalize=True).strip())) + ',' + pwd for n in names}
    name_pw_map = { n: (my_password(pwd:= generate_password(separator="", capitalize=True).strip())) + ',' + pwd for n in names}
    with Path('names.yml').open('w') as yaml_file:
        yaml.dump(name_pw_map, yaml_file)
    pp(name_pw_map, width=200, compact=True)
