#!/usr/bin/env python3
# === HEADER START ===
# SPDX-License-Identifier: MIT
# Copyright © 2025 Benjamin Hoch
#
# @NAME         : agent_mailstore_api
# @DESCRIPTION  : check mailstore via api
# @AUTHOR       : Benjamin Hoch <b.hoch@pronexon.de>
# @VERSION      : 1.1.0
# @CREATED      : 2025-06-09
# @UPDATED      : 2025-07-17
# @URL          : https://pronexon.de
# @USAGE        : z.B. './agent_mailstore_api -ip 10.115.0.211 -u test_api_user -pw "NvHZ/zMrhqEa2TcBoH/iQbgFTFgXsKdLGc6S5Ievcdk7"'
# @REQUIRES     :
#
# @SETUP:
#  I)    create mailstore api user
#  II)   activate mailstore api
#
# @NOTES:
#       -
#
# @HISTORY:
#   Version   Date        Author             Description
#   -------   ----------  -----------------  ------------------------------
#   1.0.0     2025-06-09  Benjamin Hoch      initial release
#   1.1.0     2025-07-17  Benjamin Hoch      added support for multiple jobs
#
# @TODO:
#   -
# === HEADER END ===

import argparse
import json
import requests
import traceback

from datetime import datetime, timedelta
from requests.auth import HTTPBasicAuth
from requests.packages.urllib3.exceptions import InsecureRequestWarning

# suppress insecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

# payload
payload_instances = {
    "instanceFilter": "*"  # example: get all instances (filter as a string)
}

# argument parsing
parser = argparse.ArgumentParser(
        prog="Mailstore-API",
        description="get job info via the mailstore api",
        epilog="pronexon gmbh")
parser.add_argument("--ip_address", "-ip", required=True, help="mailstore server ip")
parser.add_argument("--username", "-u", required=True, help="mailstore api user")
parser.add_argument("--password", "-pw", required=True, help="mailstore api password")
parser.add_argument("--msp", "-msp", action="store_true", help="set if mailstore msp is used, default is no msp")
parser.add_argument("--debug", "-d", action="store_true", help="set for detailed debugging output")

args = parser.parse_args()

# debug
debug = False

if args.debug:
    debug = args.debug
    print("starting script...")


# helper function to parse json response and handle errors
def parse_json_response(response):
    try:
        # decode the response content using utf-8-sig to handle BOM if present
        decoded_response = response.content.decode('utf-8-sig')
        return json.loads(decoded_response)  # parse the JSON
    except ValueError as e:
        if debug:
            print(f"failed to parse json response: {e}")
        return None


def get_request(url, payload=None):
    try:
        response = requests.post(url, data=payload, auth=HTTPBasicAuth(args.username, args.password),
                                 headers={'Content-Type': 'application/x-www-form-urlencoded'}, verify=False, timeout=30)

        if response.status_code == 200:
            if debug: print("request successful.")
            # use the parse_json_response helper function
            response_json = parse_json_response(response)

            if response_json and "result" in response_json:
                return response_json["result"]
            else:
                if debug:
                    print(f"unexpected response structure: {response_json}")
                return False
        else:
            if debug:
                print(f"request failed with status code {response.status_code}")
                print("response:", response.text)
            return False
    except Exception as e:
        if debug: print(f"request failed: {e} for url: {url}")
        raise Exception(f"request failed: {e} for url: {url}")


def get_time_range():
    today = datetime.now()
    from_time = (today - timedelta(hours=24)).strftime("%Y-%m-%dT%H:%M:%S")
    to_time = today.strftime("%Y-%m-%dT%H:%M:%S")
    return from_time, to_time

def split_jobs_by_profile(job_data):
    """
    Nimmt eine Liste von Jobs (als Dictionaries) und gruppiert sie nach 'profileName'.
    Gibt ein Dictionary zurück, wobei jeder Schlüssel ein profileName ist und der Wert
    die Liste der zugehörigen Jobs ist.

    example job_data:

    {
    "error": null,
    "token": null,
    "statusVersion": 2,
    "statusCode": "succeeded",
    "percentProgress": null,
    "statusText": null,
    "result": [
        {
            "id": 2192,
            "userName": "admin",
            "profileId": 4,
            "profileName": "AllePostfaecher",
            "result": "succeeded",
            "itemsArchived": 20,
            "startTime": "2026-09-02T23:59:03",
            "completeTime": "2026-09-03T00:14:42",
            "machineName": "pnrtmssrv01"
        },...
    """
    job_groups = {}

    for job in job_data:
        if debug: print(f"splitting up job {job}")
        profile_name = job.get('profileName', 'unknown')

        if profile_name not in job_groups:
            job_groups[profile_name] = []

        job_groups[profile_name].append(job)

    return dict(job_groups)

if __name__ == "__main__":
    try:
        
        from_time, to_time = get_time_range()
        
        # prepare list, here the gathered job_data will be stored
        job_data = {}
        
        # check if msp and build the corresponding endpoint urls
        if args.msp:
            
            # gathered cert_data will be stored here - we will need a dict, because of the instances
            cert_data = {}

            # msp
            url_instances = f"https://{args.ip_address}:8474/api/invoke/GetInstances"
            url_workers = f"https://{args.ip_address}:8474/api/invoke/GetWorkerResults"
            url_credentials = f"https://{args.ip_address}:8474/api/invoke/GetCredentials"

            # get instances
            instances = get_request(url_instances, payload_instances)

            # go through each instance and get the corresponding worker result
            for instance in instances:
                instance_id = instance["instanceID"]
                if debug: print(f"Instance: {instance['instanceID']}")
                if debug: print(f"From-Time: {from_time}")
                if debug: print(f"To-Time: {to_time}")
                payload_workers = {
                        "instanceID": instance["instanceID"],
                        "fromIncluding": from_time,
                        "toExcluding": to_time,
                        "timeZoneID": "$Local",
                }

                # query worker results
                worker_result = get_request(url_workers, payload_workers)
                if debug: print(f"Job-count: {len(worker_result)}")
                if debug: print("worker result:")
                if debug: print(worker_result)

                # split worker results in jobs
                if debug: print("splitting worker result by profile name")

                worker_result_splitted = split_jobs_by_profile(worker_result)

                if debug: print(f"splitted worker results by profile name for instance: {instance['instanceID']}")
                if debug: print(worker_result_splitted)

                for profile_name, jobs in worker_result_splitted.items():
                    stats = {
                        # possible known job results
                        "succeeded": 0,
                        "failed" :0,
                        "cancelled": 0,
                        "disconnected": 0,
                        "threadAbort": 0,
                        "completedWithErrors": 0,

                        # fallback job result if none of the above match
                        "unknown": 0
                    }
                    
                    # count the job results and place them in a job_data strucutre like this:
                    # job_data structure:
                    #
                    # {
                    #     "<instance_id>": {
                    #         "<profile_name>": {
                    #             "succeeded": int,
                    #             "failed": int,
                    #             "cancelled": int,
                    #             "disconnected": int,
                    #             "threadAbort": int,
                    #             "completedWithErrors": int,
                    #             "unknown": int
                    #         }
                    #     }
                    # }

                    for job in jobs:
                        result = job.get("result")

                        if result in stats:
                            stats[result] += 1
                        else:
                            stats["unknown"] += 1

                    if instance_id not in job_data:
                        job_data[instance_id] = {}

                    if profile_name:
                        job_data[instance_id][profile_name] = stats
                    else:
                        job_data[instance_id]["unknown"] = stats


                # gather the credentials for each instance
                # {
                #   "ID": 1,
                #   "Type": "Office365_Modern",
                #   "Description": "XY GmbH",
                #   "Settings": {
                #       "ApplicationId": "6ba8b0c0-6c34-4d52-9cde-cd8e653e3ea3",
                #       "TenantId": "030-a875-4ec8-8a0c-7c36e8bee254",
                #       "CertificateThumbprint": "0D9C4C5F9F6DEA710E2571E68F10842E2A32",
                #       "CertificateExpirationDate": "2036-10-15T09:04:44Z"
                #   }
                # }

                # to get the data we need to specify the instance id
                payload_credentials = {
                        "instanceID": instance_id,
                }

                instance_credentials = get_request(url_credentials, payload_credentials)

                if debug: print(f"credentials for instance {instance_id}: {instance_credentials}")

                for credential in instance_credentials:
                    if debug: print(f"certificate expiry date for instance {instance_id} type {credential['Type']}: {credential['Settings']['CertificateExpirationDate']}")

                    if instance_id not in cert_data:
                        if debug: print(f"adding {instance_id} to cert_data")
                        cert_data[instance_id] = []

                    cert_data[instance_id].append(credential)


            print("<<<mailstore_msp_job:sep(0)>>>")
            print(job_data)

            print("<<<mailstore_msp_cert:sep(0)>>>")
            print(cert_data)


        # has to be no msp so use different endpoint urls
        else:
            
            # gathered cert_data will be stored here - we will only need a list
            cert_data = []
            
            # no msp
            url_workers = f"https://{args.ip_address}:8463/api/invoke/GetWorkerResults"
            url_credentials = f"https://{args.ip_address}:8463/api/invoke/GetCredentials"

            # prepare the payload
            payload_workers = {
                "fromIncluding": from_time,
                "toExcluding": to_time,
                "timeZoneID": "$Local",
            }

            # query worker results
            worker_result = get_request(url_workers, payload_workers)

            # split worker results in jobs
            worker_result_splitted = split_jobs_by_profile(worker_result)

            for profile_name, jobs in worker_result_splitted.items():
                stats = {
                    # possible known job results
                    "succeeded": 0,
                    "failed" :0,
                    "cancelled": 0,
                    "disconnected": 0,
                    "threadAbort": 0,
                    "completedWithErrors": 0,

                    # fallback job result if none of the above match
                    "unknown": 0
                }

                # count the job results and place them in a job_data strucutre like this:
                # job_data structure:
                #
                # {
                #     "<profile_name>": {
                #         "succeeded": int,
                #         "failed": int,
                #         "cancelled": int,
                #         "disconnected": int,
                #         "threadAbort": int,
                #         "completedWithErrors": int,
                #         "unknown": int
                #    }
                # }

                for job in jobs:
                    result = job.get("result")

                    if result in stats:
                        stats[result] += 1
                    else:
                        stats["unknown"] += 1

                if profile_name:
                    job_data[profile_name] = stats
                else:
                    job_data['unknown'] = stats
            
            # gather the credentials
            # {
            #   "ID": 1,
            #   "Type": "Office365_Modern",
            #   "Description": "XY GmbH",
            #   "Settings": {
            #       "ApplicationId": "6ba8b0c0-6c34-4d52-9cde-cd8e653e3ea3",
            #       "TenantId": "030-a875-4ec8-8a0c-7c36e8bee254",
            #       "CertificateThumbprint": "0D9C4C5F9F6DEA710E2571E68F10842E2A32",
            #       "CertificateExpirationDate": "2036-10-15T09:04:44Z"
            #   }
            # }

            credentials = get_request(url_credentials)
            if debug: print(f"credentials retrieved: {credentials}")

            for credential in credentials:
                cert_data.append(credential)
                

            print("<<<mailstore_no_msp_job:sep(0)>>>")
            print(job_data)
            print("<<<mailstore_no_msp_cert:sep(0)>>>")
            print(cert_data)


    except Exception as e:
        if debug: print(f"Error: {e}")
        if debug: traceback.print_exc()
        if args.msp:
            print("<<<mailstore_msp_job:sep(0)>>>")
            print(None)
            print("<<<mailstore_msp_cert:sep(0)>>>")
            print(None)
        else:
            print("<<<mailstore_no_msp_job:sep(0)>>>")
            print(None)
            print("<<<mailstore_no_msp_cert:sep(0)>>>")
            print(None)


