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

class ASA_Telemetry {

	const POLICY_OPTION         = 'asa_telemetry_policy';
	const SNAPSHOT_OPTION       = 'asa_telemetry_snapshot';
	const SNAPSHOT_HISTORY      = 'asa_snapshot_diff_history';
	const FILE_DRIFT_LOG_OPTION = 'asa_file_drift_timeline';

	public static function get_default_policy() {
		return array(
			'push_enabled' => 0,
			'categories'   => array(
				'plugin_theme_core_versions' => 0,
				'integrity_hashes'           => 0,
				'admin_user_inventory'       => 0,
				'cron_event_inventory'       => 0,
				'backup_restore_telemetry'   => 0,
				'snapshot_diffs'             => 0,
				'file_drift_timeline'        => 0,
				'privileged_config_states'   => 0,
				'debug_log_fetch'            => 0,
				'file_log_fetch'             => 0,
				'wp_activity_log_fetch'      => 0,
			),
		);
	}

	public static function get_category_labels() {
		return array(
			'plugin_theme_core_versions' => __( 'Plugin / Theme / Core Versions', 'aegisify-audit-agent' ),
			'integrity_hashes'           => __( 'Integrity Hashes', 'aegisify-audit-agent' ),
			'admin_user_inventory'       => __( 'Admin / User Inventory', 'aegisify-audit-agent' ),
			'cron_event_inventory'       => __( 'Cron / Event Inventory', 'aegisify-audit-agent' ),
			'backup_restore_telemetry'   => __( 'Backup / Restore Telemetry', 'aegisify-audit-agent' ),
			'snapshot_diffs'             => __( 'Snapshot Diffs', 'aegisify-audit-agent' ),
			'file_drift_timeline'        => __( 'File Drift Timeline', 'aegisify-audit-agent' ),
			'privileged_config_states'   => __( 'Privileged Options / Config States', 'aegisify-audit-agent' ),
			'debug_log_fetch'            => __( 'Allow debug.log Fetch', 'aegisify-audit-agent' ),
			'file_log_fetch'             => __( 'Allow Custom Log File Fetch', 'aegisify-audit-agent' ),
			'wp_activity_log_fetch'      => __( 'Allow WordPress Activity Log Fetch', 'aegisify-audit-agent' ),
		);
	}

	public static function get_policy() {
		$stored   = get_option( self::POLICY_OPTION, array() );
		$defaults = self::get_default_policy();

		if ( ! is_array( $stored ) ) {
			$stored = array();
		}

		$policy = wp_parse_args( $stored, $defaults );
		$policy['push_enabled'] = empty( $policy['push_enabled'] ) ? 0 : 1;
		$policy['categories']   = is_array( $policy['categories'] ) ? $policy['categories'] : array();

		foreach ( $defaults['categories'] as $key => $value ) {
			$policy['categories'][ $key ] = empty( $policy['categories'][ $key ] ) ? 0 : 1;
		}

		return $policy;
	}

	public static function save_policy( array $raw_policy ) {
		$defaults = self::get_default_policy();
		$policy   = array(
			'push_enabled' => empty( $raw_policy['push_enabled'] ) ? 0 : 1,
			'categories'   => array(),
		);

		$raw_categories = isset( $raw_policy['categories'] ) && is_array( $raw_policy['categories'] ) ? $raw_policy['categories'] : array();
		foreach ( $defaults['categories'] as $category_key => $enabled ) {
			$policy['categories'][ $category_key ] = empty( $raw_categories[ $category_key ] ) ? 0 : 1;
		}

		update_option( self::POLICY_OPTION, $policy, false );
		update_option( 'asa_telemetry_policy_updated_at', current_time( 'mysql' ), false );

		return $policy;
	}

	public static function is_category_allowed( $category_key ) {
		$category_key = sanitize_key( (string) $category_key );
		if ( '' === $category_key ) {
			return false;
		}

		$policy = self::get_policy();
		return ! empty( $policy['push_enabled'] ) && ! empty( $policy['categories'][ $category_key ] );
	}

	public static function get_capabilities() {
		$policy         = self::get_policy();
		$labels         = self::get_category_labels();
		$allowed        = array();
		$blocked        = array();
		$master_enabled = ! empty( $policy['push_enabled'] );

		foreach ( $labels as $key => $label ) {
			if ( $master_enabled && ! empty( $policy['categories'][ $key ] ) ) {
				$allowed[ $key ] = $label;
			} else {
				$blocked[ $key ] = $label;
			}
		}

		return array(
			'push_enabled'       => $master_enabled,
			'allowed_categories' => $allowed,
			'blocked_categories' => $blocked,
			'privacy_mode'       => 'metadata_only_no_customer_content',
		);
	}

	public static function build_payload() {
		$capabilities = self::get_capabilities();
		$payload      = array(
			'generated_at_gmt' => gmdate( 'Y-m-d H:i:s' ),
			'privacy'          => array(
				'mode'        => 'metadata_only',
				'description' => 'No customer PII, PCI, PHI, post content, comment content, order content, user emails, usernames, secrets, tokens, API keys, or database row content are included.',
			),
			'policy'           => $capabilities,
			'telemetry'        => array(),
		);

		if ( empty( $capabilities['push_enabled'] ) ) {
			return $payload;
		}

		foreach ( array_keys( $capabilities['allowed_categories'] ) as $category_key ) {
			switch ( $category_key ) {
				case 'plugin_theme_core_versions':
					$payload['telemetry'][ $category_key ] = self::collect_versions();
					break;
				case 'integrity_hashes':
					$payload['telemetry'][ $category_key ] = self::collect_integrity_hashes();
					break;
				case 'admin_user_inventory':
					$payload['telemetry'][ $category_key ] = self::collect_user_inventory();
					break;
				case 'cron_event_inventory':
					$payload['telemetry'][ $category_key ] = self::collect_cron_inventory();
					break;
				case 'backup_restore_telemetry':
					$payload['telemetry'][ $category_key ] = self::collect_backup_restore_telemetry();
					break;
				case 'snapshot_diffs':
					$payload['telemetry'][ $category_key ] = self::collect_snapshot_diffs();
					break;
				case 'file_drift_timeline':
					$payload['telemetry'][ $category_key ] = self::collect_file_drift_timeline();
					break;
				case 'privileged_config_states':
					$payload['telemetry'][ $category_key ] = self::collect_privileged_config_states();
					break;
			}
		}

		return $payload;
	}

	private static function collect_versions() {
		global $wp_version;

		if ( ! function_exists( 'get_plugins' ) ) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}

		if ( ! function_exists( 'get_core_updates' ) ) {
			require_once ABSPATH . 'wp-admin/includes/update.php';
		}

		$plugins             = get_plugins();
		$active_plugins      = (array) get_option( 'active_plugins', array() );
		$network_active      = is_multisite() ? (array) get_site_option( 'active_sitewide_plugins', array() ) : array();
		$auto_update_option  = 'auto_update_' . 'plugins';
		$auto_updates        = (array) get_site_option( $auto_update_option, array() );
		$plugin_updates      = get_site_transient( 'update_plugins' );
		$themes              = wp_get_themes();
		$theme_updates       = get_site_transient( 'update_themes' );
		$current_theme       = wp_get_theme();
		$core_updates        = function_exists( 'get_core_updates' ) ? get_core_updates() : array();
		$plugin_inventory    = array();
		$theme_inventory     = array();

		foreach ( $plugins as $plugin_file => $plugin_data ) {
			$plugin_inventory[] = array(
				'plugin_file'      => (string) $plugin_file,
				'name'             => isset( $plugin_data['Name'] ) ? (string) $plugin_data['Name'] : '',
				'version'          => isset( $plugin_data['Version'] ) ? (string) $plugin_data['Version'] : '',
				'active'           => in_array( $plugin_file, $active_plugins, true ),
				'network_active'   => isset( $network_active[ $plugin_file ] ),
				'auto_update'      => in_array( $plugin_file, $auto_updates, true ),
				'update_available' => ! empty( $plugin_updates->response[ $plugin_file ] ),
			);
		}

		foreach ( $themes as $stylesheet => $theme ) {
			$theme_inventory[] = array(
				'stylesheet'       => (string) $stylesheet,
				'name'             => $theme->get( 'Name' ),
				'version'          => $theme->get( 'Version' ),
				'active'           => $current_theme->get_stylesheet() === $stylesheet,
				'parent'           => $theme->parent() ? $theme->parent()->get_stylesheet() : '',
				'update_available' => ! empty( $theme_updates->response[ $stylesheet ] ),
			);
		}

		return array(
			'core'    => array(
				'version'          => is_string( $wp_version ) ? $wp_version : '',
				'php_version'      => PHP_VERSION,
				'locale'           => get_locale(),
				'multisite'        => is_multisite(),
				'update_available' => ! empty( $core_updates ) && ! empty( $core_updates[0]->response ) && 'latest' !== $core_updates[0]->response,
			),
			'plugins' => $plugin_inventory,
			'themes'  => $theme_inventory,
		);
	}

	private static function collect_integrity_hashes() {
		if ( ! function_exists( 'get_plugins' ) ) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}

		$plugins        = get_plugins();
		$themes         = wp_get_themes();
		$current_theme  = wp_get_theme();
		$plugin_hashes  = array();
		$theme_hashes   = array();
		$core_files     = array(
			ABSPATH . 'wp-includes/version.php',
			ABSPATH . 'wp-admin/index.php',
			ABSPATH . 'wp-settings.php',
			ABSPATH . 'wp-config.php',
		);
		$core_hashes    = array();

		foreach ( $plugins as $plugin_file => $plugin_data ) {
			$full_path = WP_PLUGIN_DIR . '/' . ltrim( $plugin_file, '/' );
			if ( is_readable( $full_path ) && is_file( $full_path ) ) {
				$plugin_hashes[] = array(
					'plugin_file' => (string) $plugin_file,
					'hash'        => hash_file( 'sha256', $full_path ),
				);
			}
		}

		foreach ( $themes as $stylesheet => $theme ) {
			$style_css = trailingslashit( $theme->get_stylesheet_directory() ) . 'style.css';
			if ( is_readable( $style_css ) && is_file( $style_css ) ) {
				$theme_hashes[] = array(
					'stylesheet' => (string) $stylesheet,
					'active'     => $current_theme->get_stylesheet() === $stylesheet,
					'hash'       => hash_file( 'sha256', $style_css ),
				);
			}
		}

		foreach ( $core_files as $core_file ) {
			if ( is_readable( $core_file ) && is_file( $core_file ) ) {
				$core_hashes[] = array(
					'file' => ltrim( str_replace( ABSPATH, '', $core_file ), '/' ),
					'hash' => hash_file( 'sha256', $core_file ),
				);
			}
		}

		return array(
			'algorithm' => 'sha256',
			'core'      => $core_hashes,
			'plugins'   => $plugin_hashes,
			'themes'    => $theme_hashes,
		);
	}

	private static function collect_user_inventory() {
		$roles            = array();
		$privileged_roles = array( 'administrator', 'editor', 'shop_manager', 'super_admin' );
		$wp_roles         = wp_roles();
		$users            = count_users();
		$role_counts      = isset( $users['avail_roles'] ) && is_array( $users['avail_roles'] ) ? $users['avail_roles'] : array();

		foreach ( $role_counts as $role => $count ) {
			$roles[] = array(
				'role'       => (string) $role,
				'label'      => isset( $wp_roles->roles[ $role ]['name'] ) ? (string) $wp_roles->roles[ $role ]['name'] : (string) $role,
				'user_count' => (int) $count,
				'privileged' => in_array( $role, $privileged_roles, true ),
			);
		}

		$recent_users_query = new WP_User_Query(
			array(
				'fields'      => 'ID',
				'number'      => 1,
				'count_total' => true,
				'date_query'  => array(
					array(
						'after'     => gmdate( 'Y-m-d H:i:s', time() - ( DAY_IN_SECONDS * 30 ) ),
						'inclusive' => true,
						'column'    => 'user_registered',
					),
				),
			)
		);
		$recent_users       = (int) $recent_users_query->get_total();

		$super_admin_count = is_multisite() ? count( (array) get_super_admins() ) : 0;

		return array(
			'total_users'                => isset( $users['total_users'] ) ? (int) $users['total_users'] : 0,
			'recent_users_last_30_days'  => $recent_users,
			'roles'                      => $roles,
			'privileged_user_count'      => (int) ( ( isset( $role_counts['administrator'] ) ? $role_counts['administrator'] : 0 ) + ( isset( $role_counts['editor'] ) ? $role_counts['editor'] : 0 ) + ( isset( $role_counts['shop_manager'] ) ? $role_counts['shop_manager'] : 0 ) + $super_admin_count ),
			'super_admin_count'          => $super_admin_count,
			'has_super_admins'           => $super_admin_count > 0,
			'customer_content_included'  => false,
			'usernames_or_emails_sent'   => false,
		);
	}

	private static function collect_cron_inventory() {
		$cron           = _get_cron_array();
		$events         = array();
		$schedule_count = array();

		if ( is_array( $cron ) ) {
			foreach ( $cron as $timestamp => $hooks ) {
				foreach ( $hooks as $hook => $instances ) {
					foreach ( $instances as $instance ) {
						$schedule = isset( $instance['schedule'] ) ? (string) $instance['schedule'] : 'single';
						if ( ! isset( $schedule_count[ $schedule ] ) ) {
							$schedule_count[ $schedule ] = 0;
						}
						$schedule_count[ $schedule ]++;
						$events[] = array(
							'hook'          => (string) $hook,
							'schedule'      => $schedule,
							'next_run_gmt'  => gmdate( 'Y-m-d H:i:s', (int) $timestamp ),
							'args_count'    => isset( $instance['args'] ) && is_array( $instance['args'] ) ? count( $instance['args'] ) : 0,
						);
					}
				}
			}
		}

		usort(
			$events,
			function ( $a, $b ) {
				return strcmp( $a['next_run_gmt'], $b['next_run_gmt'] );
			}
		);

		return array(
			'total_events'     => count( $events ),
			'schedules'        => $schedule_count,
			'next_100_events'  => array_slice( $events, 0, 100 ),
			'args_values_sent' => false,
		);
	}

	private static function collect_backup_restore_telemetry() {
		$plugin_hits = array();
		$patterns    = array(
			'updraftplus'      => 'updraft',
			'backupbuddy'      => 'backupbuddy',
			'blogvault'        => 'blogvault',
			'duplicator'       => 'duplicator',
			'all_in_one_wp_migration' => 'all-in-one-wp-migration',
			'aegisify_backup'  => 'aegis',
		);
		$active_plugins = (array) get_option( 'active_plugins', array() );
		foreach ( $patterns as $label => $needle ) {
			$plugin_hits[ $label ] = false;
			foreach ( $active_plugins as $plugin_file ) {
				if ( false !== strpos( strtolower( (string) $plugin_file ), strtolower( $needle ) ) ) {
					$plugin_hits[ $label ] = true;
					break;
				}
			}
		}

		$backup_dirs = array(
			WP_CONTENT_DIR . '/uploads',
			WP_CONTENT_DIR . '/updraft',
			WP_CONTENT_DIR . '/ai1wm-backups',
			WP_CONTENT_DIR . '/backups',
			WP_CONTENT_DIR . '/upgrade-temp-backup',
		);
		$file_extensions  = array( 'zip', 'gz', 'sql', 'tar', 'wpress', 'bak' );
		$recent_backup_count = 0;
		$latest_backup_gmt   = '';
		$total_backup_bytes  = 0;

		foreach ( $backup_dirs as $backup_dir ) {
			if ( ! is_dir( $backup_dir ) ) {
				continue;
			}

			try {
				$iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $backup_dir, FilesystemIterator::SKIP_DOTS ) );
				foreach ( $iterator as $file ) {
					if ( ! $file->isFile() ) {
						continue;
					}
					if ( ! in_array( strtolower( $file->getExtension() ), $file_extensions, true ) ) {
						continue;
					}
					$recent_backup_count++;
					$total_backup_bytes += (int) $file->getSize();
					$file_time = gmdate( 'Y-m-d H:i:s', $file->getMTime() );
					if ( '' === $latest_backup_gmt || $file_time > $latest_backup_gmt ) {
						$latest_backup_gmt = $file_time;
					}
				}
			} catch ( Exception $exception ) {
				// Keep telemetry metadata-only and non-fatal.
			}
		}

		return array(
			'backup_plugins_detected' => $plugin_hits,
			'artifact_count'          => $recent_backup_count,
			'latest_artifact_gmt'     => $latest_backup_gmt,
			'total_artifact_bytes'    => $total_backup_bytes,
			'restore_markers'         => array(
				'core_upgrader_skin' => (bool) get_option( 'auto_core_update_failed', false ),
				'maintenance_mode'   => file_exists( ABSPATH . '.maintenance' ),
			),
			'content_or_backup_payload_sent' => false,
		);
	}

	private static function collect_snapshot_diffs() {
		$current = self::build_metadata_snapshot();
		$prior   = get_option( self::SNAPSHOT_OPTION, array() );
		$diff    = self::diff_snapshots( $prior, $current );
		$entry   = array(
			'timestamp_gmt' => gmdate( 'Y-m-d H:i:s' ),
			'summary'       => $diff['summary'],
			'changes'       => $diff['changes'],
		);
		$history = get_option( self::SNAPSHOT_HISTORY, array() );
		if ( ! is_array( $history ) ) {
			$history = array();
		}
		$history[] = $entry;
		$history   = array_slice( $history, -50 );
		update_option( self::SNAPSHOT_OPTION, $current, false );
		update_option( self::SNAPSHOT_HISTORY, $history, false );

		return array(
			'current' => $current,
			'diff'    => $diff,
			'history' => $history,
		);
	}

	private static function build_metadata_snapshot() {
		if ( ! function_exists( 'get_plugins' ) ) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}

		$plugins         = get_plugins();
		$themes          = wp_get_themes();
		$role_counts     = count_users();
		$active_plugins  = (array) get_option( 'active_plugins', array() );
		$plugin_versions = array();
		$theme_versions  = array();

		foreach ( $plugins as $plugin_file => $plugin_data ) {
			$plugin_versions[ $plugin_file ] = array(
				'version' => isset( $plugin_data['Version'] ) ? (string) $plugin_data['Version'] : '',
				'active'  => in_array( $plugin_file, $active_plugins, true ),
			);
		}

		foreach ( $themes as $stylesheet => $theme ) {
			$theme_versions[ $stylesheet ] = (string) $theme->get( 'Version' );
		}

		return array(
			'plugin_versions' => $plugin_versions,
			'theme_versions'  => $theme_versions,
			'roles'           => isset( $role_counts['avail_roles'] ) ? (array) $role_counts['avail_roles'] : array(),
			'privileged'      => self::collect_privileged_config_states(),
			'cron_count'      => self::collect_cron_inventory()['total_events'],
		);
	}

	private static function diff_snapshots( $prior, $current ) {
		if ( ! is_array( $prior ) ) {
			$prior = array();
		}
		$changes = array();
		$summary = array(
			'plugins_changed'   => 0,
			'themes_changed'    => 0,
			'roles_changed'     => 0,
			'privileged_changed'=> 0,
			'cron_changed'      => 0,
		);

		$prior_plugins = isset( $prior['plugin_versions'] ) && is_array( $prior['plugin_versions'] ) ? $prior['plugin_versions'] : array();
		foreach ( $current['plugin_versions'] as $plugin_file => $plugin_meta ) {
			if ( ! isset( $prior_plugins[ $plugin_file ] ) || $prior_plugins[ $plugin_file ] !== $plugin_meta ) {
				$summary['plugins_changed']++;
				$changes['plugins'][] = array( 'plugin_file' => $plugin_file, 'current' => $plugin_meta, 'prior' => isset( $prior_plugins[ $plugin_file ] ) ? $prior_plugins[ $plugin_file ] : null );
			}
		}

		$prior_themes = isset( $prior['theme_versions'] ) && is_array( $prior['theme_versions'] ) ? $prior['theme_versions'] : array();
		foreach ( $current['theme_versions'] as $stylesheet => $version ) {
			if ( ! isset( $prior_themes[ $stylesheet ] ) || $prior_themes[ $stylesheet ] !== $version ) {
				$summary['themes_changed']++;
				$changes['themes'][] = array( 'stylesheet' => $stylesheet, 'current' => $version, 'prior' => isset( $prior_themes[ $stylesheet ] ) ? $prior_themes[ $stylesheet ] : null );
			}
		}

		if ( ( isset( $prior['roles'] ) ? $prior['roles'] : array() ) !== $current['roles'] ) {
			$summary['roles_changed'] = 1;
			$changes['roles'] = array( 'current' => $current['roles'], 'prior' => isset( $prior['roles'] ) ? $prior['roles'] : array() );
		}
		if ( ( isset( $prior['privileged'] ) ? $prior['privileged'] : array() ) !== $current['privileged'] ) {
			$summary['privileged_changed'] = 1;
			$changes['privileged'] = array( 'current' => $current['privileged'], 'prior' => isset( $prior['privileged'] ) ? $prior['privileged'] : array() );
		}
		if ( ( isset( $prior['cron_count'] ) ? (int) $prior['cron_count'] : -1 ) !== (int) $current['cron_count'] ) {
			$summary['cron_changed'] = 1;
			$changes['cron_count'] = array( 'current' => (int) $current['cron_count'], 'prior' => isset( $prior['cron_count'] ) ? (int) $prior['cron_count'] : null );
		}

		return array(
			'summary' => $summary,
			'changes' => $changes,
		);
	}

	public static function append_file_drift_events( array $events ) {
		$existing = get_option( self::FILE_DRIFT_LOG_OPTION, array() );
		if ( ! is_array( $existing ) ) {
			$existing = array();
		}
		foreach ( $events as $event ) {
			$existing[] = $event;
		}
		$existing = array_slice( $existing, -200 );
		update_option( self::FILE_DRIFT_LOG_OPTION, $existing, false );
	}

	private static function collect_file_drift_timeline() {
		$timeline = get_option( self::FILE_DRIFT_LOG_OPTION, array() );
		if ( ! is_array( $timeline ) ) {
			$timeline = array();
		}

		return array(
			'events' => array_slice( $timeline, -100 ),
			'total'  => count( $timeline ),
		);
	}

	private static function collect_privileged_config_states() {
		$permalink_structure = (string) get_option( 'permalink_structure', '' );
		$uploads             = wp_get_upload_dir();
		$uploads_basedir     = isset( $uploads['basedir'] ) ? (string) $uploads['basedir'] : '';

		return array(
			'force_ssl_admin'         => defined( 'FORCE_SSL_ADMIN' ) && FORCE_SSL_ADMIN,
			'disallow_file_edit'      => defined( 'DISALLOW_FILE_EDIT' ) && DISALLOW_FILE_EDIT,
			'disallow_file_mods'      => defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS,
			'wp_debug'                => defined( 'WP_DEBUG' ) && WP_DEBUG,
			'xmlrpc_enabled'          => apply_filters( 'xmlrpc_enabled', true ),
			'users_can_register'      => (bool) get_option( 'users_can_register', false ),
			'default_role'            => (string) get_option( 'default_role', 'subscriber' ),
			'blog_public'             => '1' === (string) get_option( 'blog_public', '1' ),
			'comment_registration'    => (bool) get_option( 'comment_registration', false ),
			'comment_moderation'      => (bool) get_option( 'comment_moderation', false ),
			'permalink_structure_set' => '' !== $permalink_structure,
			'uploads_writable'        => '' !== $uploads_basedir && wp_is_writable( $uploads_basedir ),
		);
	}
}
