#!/usr/bin/env python3
# keyCocoon/keyBunker - Credential Management
# Copyright (c) 2025 Gradient Research LLC
# All rights reserved.

"""
KeyBunker Daemon - Persistent credential service using IPC
"""

import sys
import time
import signal
import os
import threading
from datetime import datetime

# Ensure sibling modules (keyBunker.py) are importable when launched by the
# embedded webSlinger Python, whose python312._pth puts the interpreter in
# isolated mode and does NOT auto-add the script's directory to sys.path.
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

# The embedded webSlinger Python's ._pth file adds site-packages to sys.path
# raw, but does NOT process .pth files inside it. pywin32 ships pywin32.pth,
# which adds win32/, win32/lib/, and runs pywin32_bootstrap (sets DLL search
# dir). Without that processing, `import win32file` fails. Call addsitedir on
# the embedded site-packages so pywin32 becomes importable. No-op on normal
# Python distributions where site already processed it.
import site as _site
_emb_site_packages = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), '..', 'site-packages')
if os.path.isdir(_emb_site_packages):
    _site.addsitedir(_emb_site_packages)

def log_daemon(message):
    try:
        log_path = os.path.join(os.path.expanduser('~'), 'keybunker_daemon.log')
        with open(log_path, 'a', encoding='utf-8') as f:
            timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            f.write(f"[{timestamp}] {message}\n")
            f.flush()
    except:
        pass

def run_ipc_server(key_bunker, stop_event, reset_event):
    """IPC server to share session key - copied from agentAPy"""
    if not key_bunker._session_key:
        log_daemon("No session key, not starting IPC server")
        return

    if sys.platform == "win32":
        try:
            import win32pipe
            import win32file
            import pywintypes

            pipe_name = r'\\.\pipe\keybunker'
            log_daemon("Starting Windows IPC server")

            while not stop_event.is_set():
                try:
                    pipe_handle = win32pipe.CreateNamedPipe(
                        pipe_name,
                        win32pipe.PIPE_ACCESS_OUTBOUND,
                        win32pipe.PIPE_TYPE_MESSAGE | win32pipe.PIPE_WAIT,
                        1, 1024, 1024, 0, None
                    )

                    win32pipe.ConnectNamedPipe(pipe_handle, None)
                    win32file.WriteFile(pipe_handle, key_bunker._session_key)
                    win32file.CloseHandle(pipe_handle)
                    log_daemon("Served session key to client")

                except pywintypes.error as e:
                    log_daemon(f"IPC server error: {e}")
                    time.sleep(0.1)

        except ImportError:
            log_daemon("Windows IPC requires pywin32 package")

def run_command_server(key_bunker, stop_event, reset_event):
    """IPC server to receive commands like reset"""
    if sys.platform == "win32":
        try:
            import win32pipe
            import win32file
            import pywintypes

            pipe_name = r'\\.\pipe\keybunker_cmd'
            log_daemon("Starting Windows command IPC server")

            while not stop_event.is_set():
                try:
                    pipe_handle = win32pipe.CreateNamedPipe(
                        pipe_name,
                        win32pipe.PIPE_ACCESS_DUPLEX,
                        win32pipe.PIPE_TYPE_MESSAGE | win32pipe.PIPE_READMODE_MESSAGE | win32pipe.PIPE_WAIT,
                        1, 1024, 1024, 0, None
                    )

                    win32pipe.ConnectNamedPipe(pipe_handle, None)

                    # Read command
                    result, data = win32file.ReadFile(pipe_handle, 1024)
                    command = data.decode('utf-8').strip()
                    log_daemon(f"Received command: {command}")

                    if command == 'reset':
                        log_daemon("Reset command received - triggering reset")
                        reset_event.set()
                        win32file.WriteFile(pipe_handle, b'OK')
                    else:
                        win32file.WriteFile(pipe_handle, b'UNKNOWN')

                    win32file.CloseHandle(pipe_handle)

                except pywintypes.error as e:
                    log_daemon(f"Command server error: {e}")
                    time.sleep(0.1)

        except ImportError:
            log_daemon("Windows IPC requires pywin32 package")

def main():
    if len(sys.argv) < 2:
        print("Usage: keybunker_daemon.py <password> [timeout_minutes]")
        sys.exit(1)
    
    password = sys.argv[1]
    timeout_minutes = int(sys.argv[2]) if len(sys.argv) > 2 else 30  # Default 30 minutes
    
    log_daemon("=== KEYBUNKER DAEMON STARTING ===")
    log_daemon(f"PID: {os.getpid()}")
    log_daemon(f"Session timeout: {timeout_minutes} minutes")
    
    try:
        from keyBunker import keyBunker
        log_daemon("keyBunker imported successfully")
        
        key_bunker = keyBunker()
        log_daemon("keyBunker instance created")
        
        # Authentication flow from agentAPy
        key_bunker.register(password)
        log_daemon("keyBunker registration completed")
        
        if key_bunker._session_key:
            key_bunker.complete_initialization()
            log_daemon("keyBunker initialization completed")
        else:
            log_daemon("ERROR: No session key after registration")
            sys.exit(1)
        
        # Track authentication time
        auth_time = time.time()
        timeout_seconds = timeout_minutes * 60
        
        # Start IPC servers
        stop_event = threading.Event()
        reset_event = threading.Event()

        ipc_thread = threading.Thread(target=run_ipc_server, args=(key_bunker, stop_event, reset_event), daemon=True)
        ipc_thread.start()
        log_daemon("IPC server thread started")

        cmd_thread = threading.Thread(target=run_command_server, args=(key_bunker, stop_event, reset_event), daemon=True)
        cmd_thread.start()
        log_daemon("Command server thread started")

        def signal_handler(signum, frame):
            log_daemon(f"Received signal {signum}, shutting down...")
            stop_event.set()
            key_bunker.clearSession()
            log_daemon("Session cleared, exiting")
            sys.exit(0)

        signal.signal(signal.SIGTERM, signal_handler)
        signal.signal(signal.SIGINT, signal_handler)

        log_daemon("KeyBunker daemon ready - serving via IPC")

        # Keep daemon alive until timeout or reset
        while True:
            time.sleep(1)

            # Check for reset command
            if reset_event.is_set():
                log_daemon("Reset requested - clearing session and exiting")
                stop_event.set()
                key_bunker.clearSession()
                log_daemon("Session cleared due to reset, exiting")
                sys.exit(0)

            # Check if session has expired
            elapsed = time.time() - auth_time
            if elapsed >= timeout_seconds:
                log_daemon(f"Session expired after {timeout_minutes} minutes")
                stop_event.set()
                key_bunker.clearSession()
                log_daemon("Session cleared due to expiration, exiting")
                sys.exit(0)
            
    except Exception as e:
        log_daemon(f"Daemon failed: {str(e)}")
        import traceback
        log_daemon(f"Traceback: {traceback.format_exc()}")
        sys.exit(1)

if __name__ == '__main__':
    main()