<?php
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class ASA_Logger {

	const OPTION_KEY         = 'asa_agent_runtime_logs';
	const RETENTION_OPTION_KEY = 'asa_agent_log_retention_days';
	const DEFAULT_RETENTION_DAYS = 7;
	const MAX_ENTRIES        = 500;
	const MAX_CONTEXT_ITEMS  = 25;
	const MAX_STRING_LENGTH  = 500;
	const MAX_NESTING_DEPTH  = 4;

	public function init() {
		register_shutdown_function( array( __CLASS__, 'capture_fatal_error' ) );
	}

	public static function get_categories() {
		return array(
			'connectivity' => __( 'Connectivity', 'aegisify-audit-agent' ),
			'configuration'=> __( 'Configuration', 'aegisify-audit-agent' ),
			'scan'         => __( 'Scan', 'aegisify-audit-agent' ),
			'network'      => __( 'Network', 'aegisify-audit-agent' ),
			'security'     => __( 'Security', 'aegisify-audit-agent' ),
			'ai'           => __( 'AI', 'aegisify-audit-agent' ),
			'system'       => __( 'System', 'aegisify-audit-agent' ),
		);
	}

	public static function get_statuses() {
		return array(
			'info'    => __( 'Info', 'aegisify-audit-agent' ),
			'success' => __( 'Success', 'aegisify-audit-agent' ),
			'warning' => __( 'Warning', 'aegisify-audit-agent' ),
			'error'   => __( 'Error', 'aegisify-audit-agent' ),
		);
	}

	public static function get_logs() {
		return self::prune_expired_logs();
	}

	public static function get_retention_days() {
		$retention_days = absint( get_option( self::RETENTION_OPTION_KEY, self::DEFAULT_RETENTION_DAYS ) );
		return max( 1, $retention_days );
	}

	public static function prune_expired_logs( $retention_days = null ) {
		$logs = get_option( self::OPTION_KEY, array() );
		$logs = is_array( $logs ) ? array_values( $logs ) : array();

		if ( null === $retention_days ) {
			$retention_days = self::get_retention_days();
		} else {
			$retention_days = max( 1, absint( $retention_days ) );
		}

		$cutoff = time() - ( DAY_IN_SECONDS * $retention_days );
		$pruned = array_values(
			array_filter(
				$logs,
				function ( $log ) use ( $cutoff ) {
					return self::get_entry_timestamp( $log ) >= $cutoff;
				}
			)
		);

		$pruned = array_slice( $pruned, -1 * self::MAX_ENTRIES );

		if ( count( $pruned ) !== count( $logs ) || $pruned !== $logs ) {
			update_option( self::OPTION_KEY, $pruned, false );
		}

		return $pruned;
	}

	public static function log( $category, $status, $source, $action, $message, array $context = array(), $dedupe_window = 0 ) {
		$category = self::normalize_category( $category );
		$status   = self::normalize_status( $status );
		$source   = sanitize_key( (string) $source );
		$action   = sanitize_key( (string) $action );
		$message  = sanitize_text_field( (string) $message );
		$context  = self::sanitize_context( $context );
		$logs     = self::get_logs();
		$now      = time();

		$entry = array(
			'id'            => wp_generate_uuid4(),
			'timestamp'     => current_time( 'mysql' ),
			'timestamp_gmt' => gmdate( 'Y-m-d H:i:s' ),
			'category'      => $category,
			'status'        => $status,
			'source'        => $source,
			'action'        => $action,
			'message'       => $message,
			'context'       => $context,
			'repeat_count'  => 1,
			'last_seen_at'  => current_time( 'mysql' ),
			'last_seen_ts'  => $now,
			'hash'          => md5( wp_json_encode( array( $category, $status, $source, $action, $message, $context ) ) ),
		);

		if ( $dedupe_window > 0 && ! empty( $logs ) ) {
			$last_index = count( $logs ) - 1;
			$last_entry = isset( $logs[ $last_index ] ) && is_array( $logs[ $last_index ] ) ? $logs[ $last_index ] : array();
			$last_seen  = isset( $last_entry['last_seen_ts'] ) ? absint( $last_entry['last_seen_ts'] ) : 0;
			$last_hash  = isset( $last_entry['hash'] ) ? (string) $last_entry['hash'] : '';

			if ( '' !== $last_hash && $last_hash === $entry['hash'] && $last_seen >= ( $now - absint( $dedupe_window ) ) ) {
				$logs[ $last_index ]['repeat_count'] = isset( $last_entry['repeat_count'] ) ? absint( $last_entry['repeat_count'] ) + 1 : 2;
				$logs[ $last_index ]['last_seen_at'] = $entry['last_seen_at'];
				$logs[ $last_index ]['last_seen_ts'] = $entry['last_seen_ts'];
				update_option( self::OPTION_KEY, array_slice( array_values( $logs ), -1 * self::MAX_ENTRIES ), false );
				return;
			}
		}

		$logs[] = $entry;
		$logs   = array_slice( array_values( $logs ), -1 * self::MAX_ENTRIES );
		update_option( self::OPTION_KEY, $logs, false );
	}

	public static function get_summary( array $logs = array() ) {
		$logs    = ! empty( $logs ) ? $logs : self::get_logs();
		$summary = array(
			'total'      => count( $logs ),
			'info'       => 0,
			'success'    => 0,
			'warning'    => 0,
			'error'      => 0,
			'categories' => array(),
		);

		foreach ( self::get_categories() as $category_key => $category_label ) {
			$summary['categories'][ $category_key ] = 0;
		}

		foreach ( $logs as $log ) {
			$status   = isset( $log['status'] ) ? self::normalize_status( $log['status'] ) : 'info';
			$category = isset( $log['category'] ) ? self::normalize_category( $log['category'] ) : 'system';

			if ( isset( $summary[ $status ] ) ) {
				$summary[ $status ]++;
			}
			if ( isset( $summary['categories'][ $category ] ) ) {
				$summary['categories'][ $category ]++;
			}
		}

		return $summary;
	}

	public static function capture_fatal_error() {
		$error = error_get_last();
		if ( empty( $error ) || ! is_array( $error ) ) {
			return;
		}

		$fatal_types = array( E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR );
		$type        = isset( $error['type'] ) ? absint( $error['type'] ) : 0;
		if ( ! in_array( $type, $fatal_types, true ) ) {
			return;
		}

		self::log(
			'system',
			'error',
			'php',
			'fatal_shutdown',
			__( 'A fatal PHP shutdown error was captured by the agent logger.', 'aegisify-audit-agent' ),
			array(
				'error_type' => $type,
				'file'       => isset( $error['file'] ) ? wp_normalize_path( (string) $error['file'] ) : '',
				'line'       => isset( $error['line'] ) ? absint( $error['line'] ) : 0,
				'message'    => isset( $error['message'] ) ? sanitize_text_field( (string) $error['message'] ) : '',
			),
			0
		);
	}

	private static function normalize_category( $category ) {
		$category   = sanitize_key( (string) $category );
		$categories = self::get_categories();
		return isset( $categories[ $category ] ) ? $category : 'system';
	}

	private static function normalize_status( $status ) {
		$status   = sanitize_key( (string) $status );
		$statuses = self::get_statuses();
		return isset( $statuses[ $status ] ) ? $status : 'info';
	}

	private static function sanitize_context( $context, $depth = 0, $context_key = '' ) {
		if ( $depth > self::MAX_NESTING_DEPTH ) {
			return '[depth-limited]';
		}

		if ( is_array( $context ) ) {
			$sanitized = array();
			$count     = 0;
			foreach ( $context as $key => $value ) {
				if ( $count >= self::MAX_CONTEXT_ITEMS ) {
					$sanitized['truncated'] = sprintf( 'Additional entries omitted after %d items.', self::MAX_CONTEXT_ITEMS );
					break;
				}
				$sanitized_key               = sanitize_key( (string) $key );
				$sanitized[ $sanitized_key ] = self::sanitize_context( $value, $depth + 1, $sanitized_key );
				$count++;
			}
			return $sanitized;
		}

		if ( is_object( $context ) ) {
			return self::sanitize_context( get_object_vars( $context ), $depth + 1, $context_key );
		}

		if ( is_bool( $context ) ) {
			return $context;
		}

		if ( is_int( $context ) || is_float( $context ) ) {
			return $context;
		}

		if ( null === $context ) {
			return '';
		}

		$string = sanitize_text_field( (string) $context );
		return self::redact_string( $string, $context_key );
	}

	private static function redact_string( $string, $context_key = '' ) {
		$sensitive_keys = array( 'authorization', 'cookie', 'credential', 'key', 'passwd', 'password', 'secret', 'signature', 'token' );
		if ( in_array( strtolower( (string) $context_key ), $sensitive_keys, true ) ) {
			return '[redacted]';
		}

		$replacements = array(
			'/(Authorization\s*:\s*)([^\s]+)/i'             => '$1[redacted]',
			'/(Bearer\s+)([^\s]+)/i'                         => '$1[redacted]',
			'/(X-Aegisify-Agent-(?:Key|Key-B64|Signature)\s*:\s*)([^\s]+)/i' => '$1[redacted]',
			'/([?&](?:api_key|key|token|signature|secret|password)=)([^&\s]+)/i' => '$1[redacted]',
		);
		foreach ( $replacements as $pattern => $replacement ) {
			$string = preg_replace( $pattern, $replacement, $string );
		}

		if ( strlen( $string ) > self::MAX_STRING_LENGTH ) {
			$string = substr( $string, 0, self::MAX_STRING_LENGTH ) . '…';
		}

		return $string;
	}

	private static function get_entry_timestamp( $log ) {
		if ( ! empty( $log['last_seen_ts'] ) ) {
			return absint( $log['last_seen_ts'] );
		}

		if ( ! empty( $log['timestamp_gmt'] ) ) {
			$timestamp = strtotime( (string) $log['timestamp_gmt'] . ' UTC' );
			if ( false !== $timestamp ) {
				return (int) $timestamp;
			}
		}

		if ( ! empty( $log['timestamp'] ) ) {
			$timestamp = strtotime( (string) $log['timestamp'] );
			if ( false !== $timestamp ) {
				return (int) $timestamp;
			}
		}

		return time();
	}
}
