#!/usr/bin/python3
"""ogroos-activate — OgroOS activation (digital licence, product key, organisation, offline, volume).

  ogroos-activate                       show the activation state
  ogroos-activate --json                the same as JSON (Settings and our apps use it)
  ogroos-activate --key KEY             activate with a product key (KEY "-" reads it from stdin)
  ogroos-activate --offline-request KEY show the installation ID for offline activation
  ogroos-activate --offline-confirm CID enter the confirmation ID
  ogroos-activate --volume HOST[:PORT]  activate with the organisation's activation proxy
  ogroos-activate --renew               contact the activation server now (renew / find this PC's licence)
  ogroos-activate --troubleshoot        check what stops activation and try to fix it
  ogroos-activate --transfer            "I changed hardware on this PC recently"
  ogroos-activate --auto                what the timer runs (quiet; contacts a server only when due)

Changes need an administrator: run as root, or the command asks for an administrator's password
(polkit). SPDX-FileCopyrightText: 2026 Dhaka Tek. SPDX-License-Identifier: LicenseRef-OgroOS-Proprietary
"""
import argparse
import json
import os
import sys

sys.dont_write_bytecode = True  # nothing left behind in /usr/lib after a purge
sys.path.insert(0, "/usr/lib/ogroos-activation")
import ogroos_activation as A  # noqa: E402
import ogroos_licensing as L  # noqa: E402

CHANGES = ("key", "offline_request", "offline_confirm", "volume", "renew", "troubleshoot", "transfer", "auto")


def out(args, ok, msg, status=None, **extra):
    if status is not None and "message" not in status:
        status = dict(status, message=A.message(status))
    if args.json:
        d = {"ok": ok, "message": msg, "status": status}
        d.update(extra)
        print(json.dumps(d, indent=1))
    else:
        if msg:
            print(msg)
        if status is not None and not args.quiet:
            show(status)
        for k, v in extra.items():
            if k == "steps":
                for step in v:
                    print("  [%s] %s" % ("ok" if step["ok"] else "!!", step["text"]))
            elif k == "installation_id":
                print("Installation ID: %s" % v)
    return 0 if ok else 1


def show(s):
    labels = {"activated": "Active", "grace": "Not active (grace period)", "not-activated": "Not active"}
    print("Edition:            %s" % (s.get("edition_name") or "OgroOS"))
    print("Activation state:   %s" % labels.get(s.get("state"), s.get("state")))
    print("                    %s" % (s.get("message") or A.message(s)))
    if s.get("subscription_ends"):
        print("Subscription ends:  %s" % A._dmy(s["subscription_ends"]))
    if s.get("org"):
        print("Organisation:       %s" % s["org"])
    if s.get("state") == "grace":
        print("Days left:          %d (until %s)" % (s["days_left"], A._dmy(s["grace_until"][:10])))
    if s.get("hardware_id"):
        print("Hardware ID:        %s…" % s["hardware_id"])
    elif "has_hardware_id" in s:
        print("Hardware ID:        none (a VM or a PC without a DMI UUID: use a key or your organisation)")


def troubleshoot(c):
    steps = []
    if c.ident["hw"]:
        steps.append({"ok": True, "text": "This PC has a hardware ID, so it can hold a digital licence."})
    else:
        steps.append({"ok": False, "text": "This PC has no usable hardware ID (a VM or a missing DMI UUID): a digital "
                                           "licence is not possible; activate with a product key or your organisation."})
    lic, problem = A.current_licence(c.st, c.ident, c.keys)
    if problem == "hardware-changed":
        steps.append({"ok": False, "text": "The licence on this disk belongs to other hardware. If you changed hardware "
                                           "on this PC recently, choose \"I changed hardware on this PC recently\"."})
    try:
        t = c.server_time_check()
        skew = (L.utcnow() - t).total_seconds()
        steps.append({"ok": True, "text": "The activation server can be reached (%s)." % c.server})
        if abs(skew) > 600:
            steps.append({"ok": False, "text": "This PC's clock is %d minutes %s the server's. Activation uses the "
                                               "server's time; correct the clock in Settings > Time & language."
                                               % (abs(skew) // 60, "ahead of" if skew > 0 else "behind")})
        else:
            steps.append({"ok": True, "text": "This PC's clock is correct."})
    except A.ActivationError as exc:
        steps.append({"ok": False, "text": exc.message})
    try:
        r = c.renew()
        text = {"renewed": "The activation was renewed.", "claimed": "This PC's digital licence was found and activated.",
                "enrolled": "This PC was enrolled in your organisation's subscription.",
                "leased": "Your organisation's activation service activated this PC.",
                "not-licensed": "No digital licence was found for this PC. Enter a product key, or ask your IT department.",
                "deactivated": "This PC's seat was released by Dhaka Tek or your IT department.",
                "hardware-changed": "The licence on this disk was issued for this PC's earlier hardware.",
                "revoked": "This PC's subscription was revoked."}.get(r, r)
        steps.append({"ok": r in ("renewed", "claimed", "enrolled", "leased"), "text": text})
    except A.ActivationError as exc:
        steps.append({"ok": False, "text": exc.message})
    proxy = A.conf_get(c.cp, "volume", "proxy") or c.discover_proxy()
    if proxy:
        steps.append({"ok": True, "text": "Organisation activation service: %s" % proxy})
    return steps


def main():
    ap = argparse.ArgumentParser(prog="ogroos-activate", description="OgroOS activation")
    g = ap.add_mutually_exclusive_group()
    g.add_argument("--key", metavar="KEY")
    g.add_argument("--offline-request", metavar="KEY")
    g.add_argument("--offline-confirm", metavar="CID")
    g.add_argument("--volume", metavar="HOST")
    g.add_argument("--renew", action="store_true")
    g.add_argument("--troubleshoot", action="store_true")
    g.add_argument("--transfer", action="store_true")
    g.add_argument("--auto", action="store_true")
    ap.add_argument("--json", action="store_true")
    ap.add_argument("--quiet", action="store_true")
    args = ap.parse_args()
    changing = any(getattr(args, k) for k in CHANGES)

    if os.geteuid() != 0:
        if changing:
            if args.auto:
                return out(args, False, "The timer runs this as root.")
            os.execvp("pkexec", ["pkexec", "/usr/bin/ogroos-activate"] + sys.argv[1:])
        return out(args, True, "", A.read_public_status())

    c = A.Client()
    try:
        if args.key:
            key = sys.stdin.readline().strip() if args.key == "-" else args.key
            norm, ktype = L.parse_key(key)
            if ktype == "volume":
                raise A.ActivationError("This is a volume key for an organisation's activation proxy, not for a PC.")
            c.activate("key", key=norm)
            s = c.finish()
            return out(args, True, A.message(s), s)
        if args.offline_request:
            key = sys.stdin.readline().strip() if args.offline_request == "-" else args.offline_request
            iid = c.offline_request(key)
            s = c.finish()
            return out(args, True, "On a phone or another PC, open %soffline and enter the product key and this "
                                   "installation ID; then enter the confirmation ID here." % c.server, s,
                       installation_id=iid, offline_url=c.server + "offline")
        if args.offline_confirm:
            cid = sys.stdin.readline().strip() if args.offline_confirm == "-" else args.offline_confirm
            c.offline_confirm(cid)
            s = c.finish()
            return out(args, True, A.message(s), s)
        if args.volume:
            c.volume(args.volume)
            s = c.finish()
            return out(args, True, A.message(s), s)
        if args.renew:
            r = c.renew()
            s = c.finish()
            ok = s["state"] == "activated"
            text = {"renewed": "The activation was renewed.", "claimed": "This PC's digital licence was found.",
                    "enrolled": "This PC was enrolled in your organisation's subscription.",
                    "leased": "Your organisation's activation service activated this PC.",
                    "not-licensed": "No digital licence was found for this PC."}.get(r) if ok or r == "not-licensed" else None
            return out(args, ok, text or A.message(s), s, result=r)
        if args.transfer:
            c.transfer()
            s = c.finish()
            return out(args, True, "The digital licence was moved to this PC's new hardware.", s)
        if args.troubleshoot:
            steps = troubleshoot(c)
            s = c.finish()
            return out(args, s["state"] == "activated", "Troubleshooting finished.", s, steps=steps)
        if args.auto:
            if c.due():
                try:
                    c.renew()
                except A.ActivationError:
                    pass  # no network or no licence yet: the timer tries again
            s = c.finish()
            return out(args, True, "", s if not args.quiet else None)
        s = c.finish()
        return out(args, True, "", s)
    except (A.ActivationError, L.LicenceError) as exc:
        s = c.finish()
        return out(args, False, getattr(exc, "message", str(exc)), s, error=getattr(exc, "code", "invalid"))


if __name__ == "__main__":
    sys.exit(main())
