<?php
/**
 * Writes main-coding terminal guard audit entries.
 *
 * @package Niwa_AI
 */

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

class Niwa_AI_Agent_Terminal_Audit {

	public static function append_event( $turn_id, $iteration_count, $terminal_context, $tool_call_counts, $reply ) {
		$path = self::get_path();
		if ( '' === $path ) {
			return '';
		}

		$terminal_context = is_array( $terminal_context ) ? $terminal_context : array();
		$tool_call_counts = is_array( $tool_call_counts ) ? $tool_call_counts : array();
		$guard            = isset( $terminal_context['guard'] ) ? (string) $terminal_context['guard'] : 'terminal_loop_guard';
		$last_tool        = isset( $terminal_context['last_tool'] ) ? (string) $terminal_context['last_tool'] : '';
		$prepared         = isset( $tool_call_counts['main_coding_prepared_workspaces'] ) && is_array( $tool_call_counts['main_coding_prepared_workspaces'] )
			? array_values( $tool_call_counts['main_coding_prepared_workspaces'] )
			: array();
		$steps            = isset( $tool_call_counts['main_coding_post_write_steps'] ) ? intval( $tool_call_counts['main_coding_post_write_steps'] ) : 0;
		$read_only_count  = isset( $tool_call_counts['main_coding_read_only_tool_count'] ) ? intval( $tool_call_counts['main_coding_read_only_tool_count'] ) : 0;
		if ( $read_only_count < 1 && isset( $tool_call_counts['main_coding_shell_read_only_count'] ) ) {
			$read_only_count = intval( $tool_call_counts['main_coding_shell_read_only_count'] );
		}

		$entry = array(
			'logged_at_utc'       => gmdate( 'c' ),
			'event'               => 'terminal_loop_guard',
			'plugin_version'      => defined( 'NIWA_AI_VERSION' ) ? NIWA_AI_VERSION : '',
			'turn_id'             => (string) $turn_id,
			'iterations'          => intval( $iteration_count ),
			'guard'               => $guard,
			'last_tool'           => $last_tool,
			'post_write_steps'    => $steps,
			'read_only_tool_calls' => $read_only_count,
			'prepared_workspaces' => self::sanitize_value( $prepared ),
			'terminal_context'    => self::sanitize_value( $terminal_context ),
			'suggested_recovery'  => self::recovery_hint( $guard, $steps, $read_only_count, $prepared ),
			'reply'               => self::truncate_text( (string) $reply, 800 ),
		);

		$line = function_exists( 'wp_json_encode' )
			? wp_json_encode( $entry, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE )
			: json_encode( $entry, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
		if ( is_string( $line ) && '' !== $line ) {
			$old_umask = umask( 0077 );
			try {
				@file_put_contents( $path, $line . "\n", FILE_APPEND | LOCK_EX );
			} finally {
				umask( $old_umask );
			}
			@chmod( $path, 0600 );
		}

		return $path;
	}

	private static function recovery_hint( $guard, $steps, $read_only_count, $prepared ) {
		$workspace_hint = '';
		if ( ! empty( $prepared ) && is_array( $prepared ) ) {
			$work_dirs = array();
			foreach ( $prepared as $item ) {
				if ( is_array( $item ) && ! empty( $item['work_dir'] ) ) {
					$work_dirs[] = (string) $item['work_dir'];
				}
			}
			$work_dirs = array_values( array_unique( $work_dirs ) );
			if ( ! empty( $work_dirs ) ) {
				$workspace_hint = ' Existing work copy directories: ' . implode( ', ', array_slice( $work_dirs, 0, 5 ) ) . '.';
			}
		}

		if ( 'main_coding_post_write_step_limit' === (string) $guard ) {
			return 'Post-write guard stopped after ' . intval( $steps ) . ' non-verification steps. Resume by inspecting existing work copies, then run main_coding_git_diff, main_coding_lint_check, optional test_run, and main_coding_workspace_apply instead of preparing duplicate workspaces.' . $workspace_hint;
		}

		if ( 'main_coding_read_only_shell_limit' === (string) $guard ) {
			return 'Read-only shell guard stopped after ' . intval( $read_only_count ) . ' inspection calls. Resume with a concrete main_coding_workspace_prepare, a scoped work-copy edit, or a blocker summary.' . $workspace_hint;
		}

		if ( 'main_coding_read_only_tool_limit' === (string) $guard ) {
			return 'Read-only coding guard stopped after ' . intval( $read_only_count ) . ' inspection calls across code/file/shell tools. Resume with main_coding_workspace_prepare, an existing work-copy edit, main_coding_workspace_apply, or a blocker summary.' . $workspace_hint;
		}

		if ( 'main_coding_post_prepare_step_limit' === (string) $guard ) {
			return 'Post-prepare guard stopped because a workspace was prepared but no work-copy edit/apply/rollback followed. Resume using the existing operation_id and work_path files; do not prepare a duplicate workspace.' . $workspace_hint;
		}

		return 'Terminal loop guard stopped this turn. Check prepared workspaces and continue from the last durable main-coding audit entry.' . $workspace_hint;
	}

	private static function get_path() {
		$workspace = class_exists( 'Niwa_AI_Admin_Autonomy_Workspace' )
			? Niwa_AI_Admin_Autonomy_Workspace::get_private_workspace_root()
			: '';
		if ( '' === $workspace ) {
			return '';
		}

		$dir = self::normalize_path( rtrim( $workspace, "/\\" ) . '/main-coding/audit' );
		$old_umask = umask( 0077 );
		try {
			if ( ! is_dir( $dir ) ) {
				if ( function_exists( 'wp_mkdir_p' ) ) {
					wp_mkdir_p( $dir );
				} else {
					@mkdir( $dir, 0700, true );
				}
			}
		} finally {
			umask( $old_umask );
		}

		if ( ! is_dir( $dir ) ) {
			return '';
		}

		@chmod( $dir, 0700 );
		$old_umask = umask( 0077 );
		try {
			@file_put_contents( rtrim( $dir, "/\\" ) . '/.htaccess', "Deny from all\n" );
			@file_put_contents( rtrim( $dir, "/\\" ) . '/index.php', '<?php // Silence is golden.' );
		} finally {
			umask( $old_umask );
		}
		@chmod( rtrim( $dir, "/\\" ) . '/.htaccess', 0600 );
		@chmod( rtrim( $dir, "/\\" ) . '/index.php', 0600 );

		return self::normalize_path( rtrim( $dir, "/\\" ) . '/main-coding-audit-' . gmdate( 'Y-m-d' ) . '.jsonl' );
	}

	private static function normalize_path( $path ) {
		$path = str_replace( '\\', '/', (string) $path );
		if ( function_exists( 'wp_normalize_path' ) ) {
			return wp_normalize_path( $path );
		}

		return preg_replace( '#/+#', '/', $path );
	}

	private static function sanitize_value( $value ) {
		if ( is_array( $value ) ) {
			$out = array();
			foreach ( $value as $key => $item ) {
				$key_string = is_string( $key ) ? $key : (string) $key;
				if ( preg_match( '/authorization|api[_-]?key|access[_-]?token|refresh[_-]?token|secret|password|passwd|token|bearer/i', $key_string ) ) {
					$out[ $key ] = '[redacted]';
					continue;
				}
				$out[ $key ] = self::sanitize_value( $item );
			}
			return $out;
		}

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

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

		if ( is_string( $value ) ) {
			return self::truncate_text( $value, 1200 );
		}

		return $value;
	}

	private static function truncate_text( $text, $limit ) {
		$text  = (string) $text;
		$limit = max( 80, intval( $limit ) );
		$text  = preg_replace(
			array(
				'/(Authorization:\s*Bearer\s+)[A-Za-z0-9._~+\-\/]+=*/i',
				'/(sk-[A-Za-z0-9_\-]{12,})/i',
				'/(password\s*[=:]\s*)[^\s&]+/i',
				'/(api[_-]?key\s*[=:]\s*)[^\s&]+/i',
				'/(access[_-]?token\s*[=:]\s*)[^\s&]+/i',
			),
			array(
				'$1[redacted]',
				'[redacted-api-key]',
				'$1[redacted]',
				'$1[redacted]',
				'$1[redacted]',
			),
			$text
		);

		if ( strlen( $text ) <= $limit ) {
			return $text;
		}

		return substr( $text, 0, $limit - 24 ) . "\n...[audit truncated]";
	}
}
