#!/usr/bin/env python3
"""Query current edubuntu-installer state and output JSON."""

import grp
import json
import os
import pwd
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path

METAPACKAGE_CONFIG_PATH = Path("/usr/share/edubuntu-installer/metapackages.json")

SCHEMA_DIR = Path("/usr/share/glib-2.0/schemas")

GSCHEMA_OVERRIDES = {
    "Preschool": SCHEMA_DIR / "96-edubuntu-preschool.gschema.override",
    "Primary/Elementary": SCHEMA_DIR / "96-edubuntu-primary.gschema.override",
    "Secondary/Middle/High School": SCHEMA_DIR / "96-edubuntu-secondary.gschema.override",
}

TERTIARY_LABEL = "Tertiary/College/University (Default)"

SETUP_CHOICES = [
    "Preschool",
    "Primary/Elementary",
    "Secondary/Middle/High School",
    TERTIARY_LABEL,
]

DCONF_LABELS = {"Preschool", "Primary/Elementary", "Secondary/Middle/High School"}


@dataclass
class PackageRelation:
    relation: str
    package: str


@dataclass
class MetapackageConfig:
    name: str
    show_depends: bool = True
    show_recommends: bool = True


def load_metapackage_configs():
    try:
        raw = json.loads(METAPACKAGE_CONFIG_PATH.read_text())
    except (FileNotFoundError, OSError, json.JSONDecodeError):
        return []

    entries = raw.get("metapackages")
    if not isinstance(entries, list):
        return []

    configs = []
    for entry in entries:
        if not isinstance(entry, dict):
            continue
        name = entry.get("name")
        if not isinstance(name, str) or not name.strip():
            continue
        configs.append(
            MetapackageConfig(
                name=name.strip(),
                show_depends=bool(entry.get("show_depends", True)),
                show_recommends=bool(entry.get("show_recommends", True)),
            )
        )

    return configs


def dpkg_installed(package):
    try:
        r = subprocess.run(["dpkg", "-s", package],
                           capture_output=True, text=True)
        return r.returncode == 0
    except FileNotFoundError:
        return False


def apt_cache_description(package):
    try:
        r = subprocess.run(["apt-cache", "search", "^{}$".format(package)],
                           capture_output=True, text=True)
        for line in r.stdout.strip().splitlines():
            if line.startswith(package):
                parts = line.split(" - ", 1)
                if len(parts) == 2:
                    return parts[1].strip()
    except FileNotFoundError:
        pass
    return ""


def apt_cache_depends_recommends(package, include_depends=True, include_recommends=True):
    if not include_depends and not include_recommends:
        return []

    try:
        r = subprocess.run(
            ["apt-cache", "depends", package],
            capture_output=True,
            text=True,
        )
    except FileNotFoundError:
        return []

    rows = []
    seen = set()
    for raw_line in r.stdout.splitlines():
        line = raw_line.strip()
        if not line:
            continue

        if line.startswith("|Depends:") or line.startswith("Depends:"):
            if not include_depends:
                continue
            relation = "Depends"
        elif line.startswith("|Recommends:") or line.startswith("Recommends:"):
            if not include_recommends:
                continue
            relation = "Recommends"
        else:
            continue

        dep = line.split(":", 1)[1].strip()
        if not dep or dep == "386" or dep.startswith("<"):
            continue

        key = (relation, dep)
        if key in seen:
            continue
        seen.add(key)
        rows.append(PackageRelation(relation=relation, package=dep))

    return rows


def adm_members():
    try:
        return frozenset(grp.getgrnam("adm").gr_mem)
    except KeyError:
        return frozenset()


def list_non_admin_users():
    adm = adm_members()
    users = []
    for pw in pwd.getpwall():
        if pw.pw_uid >= 1000 and pw.pw_name != "nobody" and pw.pw_name not in adm:
            users.append(pw.pw_name)
    return sorted(users)


def get_current_default():
    for label, path in GSCHEMA_OVERRIDES.items():
        if path.exists():
            return label
    return TERTIARY_LABEL


def user_marker_path(username):
    pw = pwd.getpwnam(username)
    return Path(pw.pw_dir) / ".config" / "edubuntu" / "age-group"


def get_user_default(username):
    try:
        label = user_marker_path(username).read_text().strip()
        if label in DCONF_LABELS:
            return label
    except OSError:
        pass
    return TERTIARY_LABEL


def main():
    packages = []
    for cfg in load_metapackage_configs():
        children = []
        for rel in apt_cache_depends_recommends(
            cfg.name,
            include_depends=cfg.show_depends,
            include_recommends=cfg.show_recommends,
        ):
            children.append({
                "relation": rel.relation,
                "package": rel.package,
                "installed": dpkg_installed(rel.package),
            })
        packages.append({
            "name": cfg.name,
            "description": apt_cache_description(cfg.name),
            "installed": dpkg_installed(cfg.name),
            "children": children,
        })

    users = list_non_admin_users()
    user_data = []
    for u in users:
        user_data.append({
            "username": u,
            "age_group": get_user_default(u),
        })

    json.dump({
        "packages": packages,
        "global_default": get_current_default(),
        "setup_choices": SETUP_CHOICES,
        "users": user_data,
    }, sys.stdout)


if __name__ == "__main__":
    main()
