<?php
/**
 * Dedicated admin-autonomy runtime trace sink.
 *
 * @package Niwa_AI
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Niwa_AI_Admin_Autonomy_Runtime_Trace_Logger {
	const LEVEL_LIGHT = 'light';
	const LEVEL_FULL  = 'full';

	const EVENT_LEVEL_TURN    = 'turn';
	const EVENT_LEVEL_RUNTIME = 'runtime';
	const EVENT_LEVEL_FINAL   = 'final';
	const EVENT_LEVEL_ERROR   = 'error';
	const EVENT_LEVEL_TOOL    = 'tool';
	const EVENT_LEVEL_FULL    = 'full';

	const MAX_STRING_CHARS = 1600;
	const MAX_ARRAY_ITEMS  = 60;
	const MAX_DEPTH        = 5;
	const MAX_FILE_BYTES   = 10485760;

	public function log( $event, $context = array(), $event_level = '' ) {
		$event = sanitize_key( (string) $event );
		if ( '' === $event ) {
			$event = 'runtime_event';
		}
		$context     = is_array( $context ) ? $context : array( 'value' => $context );
		$event_level = $this->normalize_event_level(
			'' !== (string) $event_level ? $event_level : $this->classify_event_level( $event, $context )
		);
		$trace_level = $this->resolve_trace_level( $event, $context, $event_level );
		if ( ! $this->should_log_event_level( $trace_level, $event_level ) ) {
			return false;
		}
		if ( self::LEVEL_LIGHT === $trace_level ) {
			$context = $this->redact_light_trace_content( $context );
		}

		$path = $this->resolve_log_path();
		if ( '' === $path ) {
			return false;
		}

		$this->rotate_if_needed( $path );

		$payload = array(
			'ts'         => gmdate( 'c' ),
			'pid'        => function_exists( 'getmypid' ) ? getmypid() : 0,
			'event'      => $event,
			'channel'    => isset( $context['channel'] ) ? sanitize_key( (string) $context['channel'] ) : '',
			'session_id' => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
			'turn_id'    => isset( $context['turn_id'] ) ? (string) $context['turn_id'] : '',
			'job_id'     => isset( $context['job_id'] ) ? (string) $context['job_id'] : ( isset( $context['turn_job_id'] ) ? (string) $context['turn_job_id'] : ( isset( $context['admin_ui_job_id'] ) ? (string) $context['admin_ui_job_id'] : '' ) ),
			'chat_id'    => isset( $context['chat_id'] ) ? (string) $context['chat_id'] : '',
			'message_id' => isset( $context['message_id'] ) ? intval( $context['message_id'] ) : 0,
			'update_id'  => isset( $context['update_id'] ) ? intval( $context['update_id'] ) : 0,
			'trace_level' => $trace_level,
			'event_level' => $event_level,
			'data'       => $this->sanitize_value( $context, 0 ),
		);

		$encoded = wp_json_encode( $payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
		if ( ! is_string( $encoded ) || '' === $encoded ) {
			return false;
		}

		$old_umask = umask( 0077 );
		try {
			$written = false !== file_put_contents( $path, $encoded . "\n", FILE_APPEND | LOCK_EX );
		} finally {
			umask( $old_umask );
		}
		if ( $written ) {
			@chmod( $path, 0600 );
		}
		return $written;
	}

	private function resolve_trace_level( $event, $context, $event_level ) {
		$level = '';
		foreach ( array( 'runtime_trace_level', 'trace_level', 'admin_autonomy_runtime_trace_level' ) as $key ) {
			if ( isset( $context[ $key ] ) && '' !== trim( (string) $context[ $key ] ) ) {
				$level = (string) $context[ $key ];
				break;
			}
		}
		$level = '' === $level ? '' : $this->normalize_trace_level( $level );

		if ( $this->is_full_trace_event_level( $event_level ) ) {
			return '' === $level ? self::LEVEL_LIGHT : $level;
		}

		if ( '' === $level && function_exists( 'get_option' ) ) {
			$option_level = get_option( 'niwa_ai_admin_autonomy_runtime_trace_level', '' );
			if ( '' !== trim( (string) $option_level ) ) {
				$level = (string) $option_level;
			}
		}

		if ( '' === $level && function_exists( 'get_option' ) && class_exists( 'Niwa_AI_Admin_Autonomy_Telegram' ) ) {
			$settings = get_option( Niwa_AI_Admin_Autonomy_Telegram::OPTION_KEY, array() );
			if ( is_array( $settings ) ) {
				if ( ! empty( $settings['runtime_trace_full'] ) ) {
					$level = self::LEVEL_FULL;
				} elseif ( isset( $settings['runtime_trace_level'] ) && '' !== trim( (string) $settings['runtime_trace_level'] ) ) {
					$level = (string) $settings['runtime_trace_level'];
				}
			}
		}

		$level = '' === $level ? self::LEVEL_LIGHT : $this->normalize_trace_level( $level );
		if ( function_exists( 'apply_filters' ) ) {
			$level = $this->normalize_trace_level(
				apply_filters(
					'niwa_ai_admin_autonomy_runtime_trace_level',
					$level,
					(string) $event,
					is_array( $context ) ? $context : array(),
					(string) $event_level
				)
			);
		}

		return $level;
	}

	private function normalize_trace_level( $level ) {
		$level = strtolower( trim( (string) $level ) );
		if ( in_array( $level, array( '1', 'true', 'yes', 'on', 'debug', 'verbose', 'all', self::LEVEL_FULL ), true ) ) {
			return self::LEVEL_FULL;
		}
		return self::LEVEL_LIGHT;
	}

	private function normalize_event_level( $event_level ) {
		$event_level = sanitize_key( (string) $event_level );
		if ( in_array( $event_level, array( self::EVENT_LEVEL_TURN, self::EVENT_LEVEL_RUNTIME, self::EVENT_LEVEL_FINAL, self::EVENT_LEVEL_ERROR, self::EVENT_LEVEL_TOOL, self::EVENT_LEVEL_FULL ), true ) ) {
			return $event_level;
		}
		return self::EVENT_LEVEL_FULL;
	}

	private function should_log_event_level( $trace_level, $event_level ) {
		if ( self::LEVEL_FULL === $trace_level ) {
			return true;
		}
		return in_array( $event_level, array( self::EVENT_LEVEL_TURN, self::EVENT_LEVEL_RUNTIME, self::EVENT_LEVEL_FINAL, self::EVENT_LEVEL_ERROR ), true );
	}

	private function is_full_trace_event_level( $event_level ) {
		return ! in_array( $event_level, array( self::EVENT_LEVEL_TURN, self::EVENT_LEVEL_RUNTIME, self::EVENT_LEVEL_FINAL, self::EVENT_LEVEL_ERROR ), true );
	}

	private function classify_event_level( $event, $context ) {
		if ( is_array( $context ) && ( ! empty( $context['error'] ) || ! empty( $context['errors'] ) || ! empty( $context['fatal'] ) ) ) {
			return self::EVENT_LEVEL_ERROR;
		}

		$event_levels = array(
			'actor_runtime_process_start' => self::EVENT_LEVEL_RUNTIME,
			'actor_runtime_process_end'   => self::EVENT_LEVEL_RUNTIME,
			'founder_chat_start'          => self::EVENT_LEVEL_TURN,
			'founder_chat_result'         => self::EVENT_LEVEL_FINAL,
			'turn_prompt_harness_ready'   => self::EVENT_LEVEL_TURN,
			'turn_runtime_built'          => self::EVENT_LEVEL_RUNTIME,
			'tool_call_start'             => self::EVENT_LEVEL_TOOL,
			'tool_call_result'            => self::EVENT_LEVEL_TOOL,
		);

		return isset( $event_levels[ $event ] ) ? $event_levels[ $event ] : self::EVENT_LEVEL_FULL;
	}

	private function resolve_log_path() {
		$base = class_exists( 'Niwa_AI_Admin_Autonomy_Workspace' )
			? Niwa_AI_Admin_Autonomy_Workspace::get_private_data_root()
			: '';
		if ( '' === $base ) {
			return '';
		}

		$dir = trailingslashit( $base ) . 'logs';
		$old_umask = umask( 0077 );
		try {
			if ( function_exists( 'wp_mkdir_p' ) ) {
				wp_mkdir_p( $dir );
			} elseif ( ! is_dir( $dir ) ) {
				mkdir( $dir, 0700, true );
			}
		} finally {
			umask( $old_umask );
		}
		@chmod( $base, 0700 );
		@chmod( $dir, 0700 );

		return is_dir( $dir ) && is_writable( $dir ) ? trailingslashit( $dir ) . 'runtime-trace.log' : '';
	}

	private function redact_light_trace_content( $value ) {
		if ( ! is_array( $value ) ) {
			return $value;
		}

		$redacted = array();
		foreach ( $value as $key => $item ) {
			$key_name = strtolower( (string) $key );
			if ( false !== strpos( $key_name, 'prompt' )
				|| false !== strpos( $key_name, 'query' )
				|| in_array( $key_name, array( 'input', 'messages', 'content', 'body', 'body_excerpt', 'text', 'text_preview', 'reply', 'output', 'instructions', 'system_prompt' ), true )
			) {
				$redacted[ $key ] = '[redacted-light-trace]';
				continue;
			}
			$redacted[ $key ] = is_array( $item ) ? $this->redact_light_trace_content( $item ) : $item;
		}

		return $redacted;
	}

	private function rotate_if_needed( $path ) {
		if ( ! is_file( $path ) || filesize( $path ) < self::MAX_FILE_BYTES ) {
			return;
		}

		$archive = $path . '.1';
		if ( is_file( $archive ) ) {
			@unlink( $archive );
		}
		@rename( $path, $archive );
	}

	private function sanitize_value( $value, $depth ) {
		if ( $depth > self::MAX_DEPTH ) {
			return '[depth-limit]';
		}

		if ( is_array( $value ) ) {
			$out   = array();
			$count = 0;
			foreach ( $value as $key => $item ) {
				$key = is_string( $key ) ? $this->sanitize_key_label( $key ) : $key;
				if ( $this->is_sensitive_key( $key ) ) {
					$out[ $key ] = '[redacted]';
				} else {
					$out[ $key ] = $this->sanitize_value( $item, $depth + 1 );
				}
				$count++;
				if ( $count >= self::MAX_ARRAY_ITEMS ) {
					$out['__truncated__'] = true;
					break;
				}
			}
			return $out;
		}

		if ( is_object( $value ) ) {
			return '[object:' . get_class( $value ) . ']';
		}

		if ( is_resource( $value ) ) {
			return '[resource]';
		}

		if ( is_string( $value ) ) {
			$value = trim( $value );
			if ( strlen( $value ) > self::MAX_STRING_CHARS ) {
				return substr( $value, 0, self::MAX_STRING_CHARS ) . '...[truncated]';
			}
			return $value;
		}

		return $value;
	}

	private function sanitize_key_label( $key ) {
		$key = preg_replace( '/[^A-Za-z0-9_:\\-\\.]/', '_', (string) $key );
		return '' === $key ? 'key' : $key;
	}

	private function is_sensitive_key( $key ) {
		$key = strtolower( (string) $key );
		foreach ( array( 'token', 'secret', 'password', 'passwd', 'authorization', 'cookie', 'api_key', 'apikey', 'nonce', 'signature' ) as $needle ) {
			if ( false !== strpos( $key, $needle ) ) {
				return true;
			}
		}
		return false;
	}
}
