# Copyright 2026 SigmaSRC, Inc. All Rights Reserved.

# Standard Python imports
import os
import sys
import time
import socket
import logging
import threading
import traceback

# Run the scheduler (and the engine/gather it spawns) in Python UTF-8 Mode so all
# text I/O defaults to UTF-8 regardless of the host locale/codepage. The service
# process itself must NOT re-exec (it would break the Windows SCM handshake), so
# we set the environment here; the scheduler child created below inherits it and
# starts in UTF-8 Mode.
os.environ["PYTHONUTF8"] = "1"
os.environ.setdefault("PYTHONIOENCODING", "utf-8")

import win32api
import win32serviceutil, win32service, servicemanager

# Sigma imports
from sigma.util import avalog, config
from sigma.sched import remote, process

# Global objects
options = config.Options()

avalog.setupLogging()
logger = logging.getLogger("winservice")
        
class SigmaService(win32serviceutil.ServiceFramework):
    _svc_name_ = options.service_name
    _svc_display_name_ = options.service_name

    event = None
    stopping = False

    def SvcStop(self):
        logger.info("Stop: shutting down scheduler...")
        self.stopping = True
        sched = remote.Scheduler()
        sched.shutdown()
        logger.info("Stop: shutdown requested")
        if self.event is not None:
            dt = 20 # Timeout, must be longer than ping-pong sleep time
            self.event.wait(dt)
            if not self.event.isSet():
                logger.warning("Stop: service didn't stop within %s seconds", dt)
            else:
                logger.info("Stop: service stopped")

    def SvcDoRun(self):
        self.event = threading.Event()
        try:
            # Tell the SCM we're running, otherwise the service start request
            # times out (~30s) while stuck in START_PENDING and Windows reports
            # "the service did not respond in a timely fashion" (error 1053).
            self.ReportServiceStatus(win32service.SERVICE_RUNNING)
            logger.info("Run: getting ready...")
            python_exe = "python.exe"
            for x in sys.path:
                x = os.path.join(x, python_exe)
                if os.path.exists(x):
                    python_exe = x
                    break
            scheduler_py = os.path.join(options.agentbin, "scheduler.py")
            n = 0
            while not self.stopping:
                logger.debug("Run: cd = %r", os.getcwd())
                logger.debug("Run: python_exe = %r", python_exe)
                logger.debug("Run: scheduler_py = %r", scheduler_py)
                logger.info("Run: starting scheduler")
                t0 = time.time()
                proc = process.createProcess([python_exe, scheduler_py, "-d"],
                                             nice=True)
                proc.wait()
                sts = proc.getstatus()
                if sts == 0:
                    logger.info("Run: scheduler exited normally; stopping")
                    break
                if sts == 78:
                    # EX_CONFIG: the agent isn't configured (blank or placeholder
                    # central server). This is permanent, not a crash -- stop the
                    # service cleanly instead of restart-looping. Mirrors the
                    # Linux unit's RestartPreventExitStatus=78.
                    logger.error("Run: agent is not configured (no central "
                                 "server). Set a server in conf\\config and "
                                 "restart the SigmaAgent service. Stopping.")
                    try:
                        servicemanager.LogErrorMsg(
                            "SigmaAgent: not configured (no central server). "
                            "Set a server in conf\\config and restart the "
                            "service.")
                    except Exception:
                        pass
                    break
                logger.warning("Run: scheduler exited with status %r", sts)
                if self.stopping:
                    break
                t1 = time.time()
                if t1 - t0 < 60:
                    n += 1
                    if n > 10:
                        logger.error("Run: scheduler keeps exiting; giving up")
                        break
                    dt = 2**n
                    logger.warning("Run: scheduler exited quickly; sleep %d", dt)
                    while dt > 0 and not self.stopping:
                        time.sleep(1)
                        dt -= 1
                else:
                    n = 0
                logger.info("Run: restarting")
            self.event.set()
        except:
            logger.exception("Run: unexpected exception")
            self.event.set()

class AvalonService(SigmaService):
    """Backwards compatibility class for 1.0 -> 1.1 upgrade."""
    _svc_name_ = "AvalonAgent"


if __name__=='__main__':
    if len(sys.argv) == 1:
        # Launched by the Service Control Manager (no arguments): host the
        # service so it reports RUNNING to the SCM. Calling HandleCommandLine
        # here instead just prints usage and exits, so the SCM start times out
        # with error 1053.
        servicemanager.Initialize()
        servicemanager.PrepareToHostSingle(SigmaService)
        servicemanager.StartServiceCtrlDispatcher()
    else:
        # Command-line use: install / remove / start / stop / debug.
        win32serviceutil.HandleCommandLine(SigmaService)
