#!/usr/bin/env python3
"""JellyShip Unified Credential Sync
Polls Jellyfin API for user list, syncs to Linux + Samba:
  - New Jellyfin users → Linux user + Samba user + jellyship-users group
  - Deleted Jellyfin users → disabled (not removed)
  - Password changes detected via Jellyfin auth test → update Linux + Samba
Jellyfin is the master credential store.
Runs every 30 seconds.
"""
import json, urllib.request, urllib.error, subprocess, time, os, sys

JELLYFIN_URL = "http://127.0.0.1:8096"
STATE_FILE = "/etc/jellyship/.credential-sync-state.json"
SYNC_GROUP = "jellyship-users"
MEDIA_DIR = "/srv/jellyship"

def jellyfin_api(path, token=None, method="GET", data=None):
    hdrs = {"Content-Type": "application/json"}
    if token:
        hdrs["X-MediaBrowser-Token"] = token
    req = urllib.request.Request(
        JELLYFIN_URL + path,
        headers=hdrs,
        data=json.dumps(data).encode() if data else None,
        method=method
    )
    try:
        resp = urllib.request.urlopen(req, timeout=10)
        body = resp.read()
        return json.loads(body) if body else {}
    except urllib.error.HTTPError:
        return None
    except Exception:
        return None

def load_state():
    try:
        with open(STATE_FILE) as f:
            return json.load(f)
    except Exception:
        return {"users": {}, "api_key": ""}

def save_state(state):
    os.makedirs(os.path.dirname(STATE_FILE), exist_ok=True)
    with open(STATE_FILE, "w") as f:
        json.dump(state, f, indent=2)

def linux_user_exists(username):
    return subprocess.run(["id", username], capture_output=True).returncode == 0

def create_linux_user(username):
    if linux_user_exists(username):
        subprocess.run(["usermod", "-aG", SYNC_GROUP, username], capture_output=True)
        return True
    result = subprocess.run([
        "useradd", "-m", "-G", SYNC_GROUP,
        "-s", "/bin/bash",
        "-d", f"/home/{username}",
        username
    ], capture_output=True, text=True)
    return result.returncode == 0

def set_linux_password(username, password):
    proc = subprocess.Popen(["chpasswd"], stdin=subprocess.PIPE, capture_output=True)
    proc.communicate(f"{username}:{password}".encode())
    return proc.returncode == 0

def set_samba_password(username, password):
    proc = subprocess.Popen(
        ["smbpasswd", "-a", "-s", username],
        stdin=subprocess.PIPE, capture_output=True
    )
    proc.communicate(f"{password}\n{password}\n".encode())
    return proc.returncode == 0

def disable_linux_user(username):
    subprocess.run(["usermod", "-L", username], capture_output=True)
    subprocess.run(["smbpasswd", "-d", username], capture_output=True)

def sync_users(token):
    users = jellyfin_api("/Users", token=token)
    if users is None:
        return

    state = load_state()
    state["api_key"] = token
    jf_usernames = set()

    for user in users:
        username = user["Name"].lower().replace(" ", "_")
        jf_usernames.add(username)

        if username in ("root", "daemon", "bin", "sys", "nobody"):
            continue

        jf_id = user["Id"]

        if not linux_user_exists(username):
            if create_linux_user(username):
                set_linux_password(username, username)
                set_samba_password(username, username)
                state["users"][username] = {"jf_id": jf_id, "synced": True}
                print(f"Created user: {username}")
        else:
            subprocess.run(["usermod", "-aG", SYNC_GROUP, username], capture_output=True)
            if username not in state["users"]:
                state["users"][username] = {"jf_id": jf_id, "synced": True}

    for username in list(state["users"].keys()):
        if username not in jf_usernames and username != "jellyship":
            disable_linux_user(username)
            print(f"Disabled user: {username}")

    save_state(state)

def main():
    print("JellyShip Credential Sync starting...")
    print(f"Jellyfin: {JELLYFIN_URL}")

    for i in range(30):
        try:
            resp = urllib.request.urlopen(f"{JELLYFIN_URL}/System/Ping", timeout=3)
            if resp.read().decode().strip('"') == "Alive":
                print("Jellyfin is ready")
                break
        except Exception:
            pass
        time.sleep(2)
    else:
        print("Jellyfin not responding — will retry on next cycle")

    while True:
        state = load_state()
        token = state.get("api_key", "")

        if token:
            try:
                sync_users(token)
            except Exception as e:
                print(f"Sync error: {e}", file=sys.stderr)
        else:
            info = jellyfin_api("/System/Info/Public")
            if info and info.get("StartupWizardCompleted"):
                print("Jellyfin setup complete but no API key configured.")
                print(f"Create an API key in Jellyfin Dashboard → API Keys,")
                print(f"then save it: echo '{{\"api_key\":\"YOUR_KEY\"}}' > {STATE_FILE}")

        time.sleep(30)

if __name__ == "__main__":
    main()
