#!/usr/local/sal/Python.framework/Versions/3.8/bin/python3


import datetime
import os
import pathlib
import plistlib
import sys

import sal
sys.path.insert(0, '/usr/local/munki')
from munkilib import munkicommon


__version__ = '1.1.0'


def main():
    # If we haven't successfully submitted to Sal, pull the existing
    # munki section rather than start from scratch, as we want to
    # keep any install/removal history that may be there.
    munki_submission = sal.get_checkin_results().get('munki', {})
    munki_report = get_managed_install_report()

    extras = {}
    extras['munki_version'] = munki_report['MachineInfo'].get('munki_version')
    extras['manifest'] = munki_report.get('ManifestName')
    extras['runtype'] = munki_report.get('RunType', 'custom')

    munki_submission['extra_data'] = extras

    munki_submission['facts'] = {
        'checkin_module_version': __version__,
        'RunType': munki_report['RunType'],
        'StartTime': munki_report['StartTime'],
        'EndTime': munki_report['EndTime'],
    }
    if munki_report.get('Conditions'):
        for condition, value in munki_report['Conditions'].items():
            # Join lists of strings into a comma-delimited string, as
            # the server wants just text.
            if hasattr(value, 'append'):
                value = ', '.join(value)
            munki_submission['facts'][condition] = value

    munki_submission['messages'] = []
    for key in ('Errors', 'Warnings'):
        for msg in munki_report[key]:
            # We need to drop the final 'S' to match Sal's message types.
            munki_submission['messages'].append({'message_type': key.upper()[:-1], 'text': msg})

    now = datetime.datetime.now().astimezone(datetime.timezone.utc).isoformat()
    # Process managed items and update histories.
    munki_submission['managed_items'] = {}

    optional_manifest = get_optional_manifest()

    for item in munki_report.get('ManagedInstalls', []):
        submission_item = {'date_managed': now}
        submission_item['status'] = 'PRESENT' if item['installed'] else 'PENDING'

        version_key = 'version_to_install' if not item['installed'] else 'installed_version'
        version = item[version_key]
        name = f'{item["name"]} {version}'
        submission_item['name'] = name

        # Pop off these two since we already used them.
        item.pop('name')
        item.pop('installed')

        item['type'] = 'ManagedInstalls'
        self_serve = 'True' if name in optional_manifest.get('managed_installs', []) else 'False'
        item['self_serve'] = self_serve
        submission_item['data'] = item
        munki_submission['managed_items'][name] = submission_item

    for item in munki_report.get('managed_uninstalls_list', []):
        submission_item = {'date_managed': now, 'status': 'ABSENT'}
        self_serve = 'True' if name in optional_manifest.get('managed_uninstalls', []) else 'False'
        submission_item['data'] = {'self_serve': self_serve, 'type': 'ManagedUninstalls'}
        munki_submission['managed_items'][item] = submission_item

    # Process InstallResults and RemovalResults into update history
    for report_key, result_type in (('InstallResults', 'PRESENT'), ('RemovalResults', 'ABSENT')):
        for item in munki_report.get(report_key, []):
            # Skip Apple software update items.
            if item.get('applesus'):
                continue
            history = {}
            # history = {'update_type': 'apple' if item.get('applesus') else 'third_party'}
            history['status'] = 'ERROR' if item.get('status') != 0 else result_type
            # This UTC datetime gets converted to a naive datetime by
            # plistlib. Fortunately, we can just tell it that it's UTC.
            history['date_managed'] = item['time'].replace(
                tzinfo=datetime.timezone.utc).isoformat()
            history['data'] = {'version': item.get('version', '0')}
            # Add over top of any pending items we may have already built.
            if item['name'] in munki_submission['managed_items']:
                munki_submission['managed_items'][item['name']].update(history)
            else:
                munki_submission['managed_items'][item['name']] = history

    sal.set_checkin_results('Munki', munki_submission)


def get_managed_install_report():
    """Return Munki ManagedInstallsReport.plist as a plist dict.

    Returns:
        ManagedInstalls report for last Munki run as a plist
        dict, or an empty dict.
    """
    # Checks munki preferences to see where the install directory is set to.
    managed_install_dir = munkicommon.pref('ManagedInstallDir')

    # set the paths based on munki's configuration.
    managed_install_report = pathlib.Path(managed_install_dir) / 'ManagedInstallReport.plist'

    try:
        munki_report = plistlib.loads(managed_install_report.read_bytes())
    except (IOError, plistlib.InvalidFileException):
        munki_report = {}

    if 'MachineInfo' not in munki_report:
        munki_report['MachineInfo'] = {}

    return sal.unobjctify(munki_report)


def get_optional_manifest():
    """Return Munki SelfServeManifest as a plist dict.

    Returns:
        SelfServeManifest for last Munki run as a plist
        dict, or an empty dict.
    """
    # Checks munki preferences to see where the install directory is set to.
    managed_install_dir = munkicommon.pref('ManagedInstallDir')

    # set the paths based on munki's configuration.
    optional_manifest_path = pathlib.Path(managed_install_dir) / 'manifests/SelfServeManifest'

    try:
        optional_manifest = plistlib.loads(optional_manifest_path.read_bytes())
    except (IOError, plistlib.InvalidFileException):
        optional_manifest = {}

    return optional_manifest


if __name__ == "__main__":
    main()