"""Eigenes Benutzerkonto. UI-frei.

Konto-Infos aus pwd/grp. Passwort-Änderung des eigenen Users über `passwd`,
das interaktiv ist — wir treiben es über ein PTY (kein root, kein Terminal).
"""
from __future__ import annotations

import grp
import os
import pwd
import select
import time
from dataclasses import dataclass, field


@dataclass
class Account:
    username: str
    fullname: str
    admin: bool                       # in 'wheel'
    groups: list[str] = field(default_factory=list)


def current() -> Account:
    info = pwd.getpwuid(os.getuid())
    fullname = info.pw_gecos.split(",")[0].strip() or info.pw_name
    gids = os.getgrouplist(info.pw_name, info.pw_gid)
    names = []
    for gid in gids:
        try:
            names.append(grp.getgrgid(gid).gr_name)
        except KeyError:
            pass
    return Account(info.pw_name, fullname, "wheel" in names, sorted(names))


def _groups_of(name: str, gid: int) -> list[str]:
    names = []
    for g in os.getgrouplist(name, gid):
        try:
            names.append(grp.getgrgid(g).gr_name)
        except KeyError:
            pass
    return names


def list_users() -> list[Account]:
    """Menschliche Konten (UID 1000–64999, ohne nobody)."""
    me = os.getuid()
    out: list[Account] = []
    for info in pwd.getpwall():
        if 1000 <= info.pw_uid < 65000 and info.pw_name != "nobody":
            groups = _groups_of(info.pw_name, info.pw_gid)
            fullname = info.pw_gecos.split(",")[0].strip() or info.pw_name
            acc = Account(info.pw_name, fullname, "wheel" in groups, sorted(groups))
            out.append(acc)
    out.sort(key=lambda a: a.username != pwd.getpwuid(me).pw_name)  # eigener zuerst
    return out


def add_user_cmds(username: str, fullname: str, admin: bool) -> list[list[str]]:
    """Befehls-Queue: Konto anlegen, optional zu wheel, Passwort-Reset erzwingen."""
    cmds = [["useradd", "-m", "-c", fullname or username, username]]
    if admin:
        cmds.append(["gpasswd", "-a", username, "wheel"])
    cmds.append(["passwd", "-e", username])   # neuer User setzt PW beim 1. Login
    return cmds


def delete_user_argv(username: str) -> list[str]:
    return ["userdel", "-r", username]


def change_password(current_pw: str, new_pw: str, timeout: float = 15.0) -> tuple[bool, str]:
    """Eigenes Passwort via passwd über ein PTY ändern. (ok, message)."""
    import pty

    pid, fd = pty.fork()
    if pid == 0:  # child
        try:
            os.execvp("passwd", ["passwd"])
        except OSError:
            os._exit(127)

    answers = [current_pw, new_pw, new_pw]
    sent = 0
    out = b""
    deadline = time.monotonic() + timeout
    while time.monotonic() < deadline:
        r, _, _ = select.select([fd], [], [], 0.4)
        if fd not in r:
            continue
        try:
            chunk = os.read(fd, 4096)
        except OSError:
            break
        if not chunk:
            break
        out += chunk
        tail = chunk.decode(errors="ignore").rstrip()
        if tail.endswith(":") and sent < len(answers):
            os.write(fd, (answers[sent] + "\n").encode())
            sent += 1

    try:
        _, status = os.waitpid(pid, 0)
    except OSError:
        status = 1
    ok = os.WIFEXITED(status) and os.WEXITSTATUS(status) == 0
    text = out.decode(errors="ignore")
    msg = "Password changed." if ok else _error_hint(text)
    return ok, msg


def _error_hint(text: str) -> str:
    low = text.lower()
    if "authentication" in low or "incorrect" in low:
        return "Current password is incorrect."
    if "too short" in low or "dictionary" in low or "simplistic" in low or "bad" in low:
        return "New password is too weak."
    return "Could not change the password."
