<?php
/**
 * Logger class.
 *
 * @package Altify_AI
 */

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

/**
 * Stores and retrieves plugin logs.
 */
class Altify_AI_Logger {
	/**
	 * Option key used to store logs.
	 */
	const OPTION_KEY = 'altify_ai_logs';

	/**
	 * Maximum number of stored history rows.
	 */
	const MAX_LOGS = 1000;

	/**
	 * Add a log entry.
	 *
	 * @param string $level   Log level.
	 * @param string $message Log message.
	 * @param array  $context Additional context.
	 *
	 * @return void
	 */
	public function log($level, $message, $context = array()) {
		$level   = strtoupper(sanitize_key((string) $level));
		$message = sanitize_text_field((string) $message);
		$context = $this->sanitize_context($context);

		if ('' === $level) {
			$level = 'INFO';
		}

		$logs = get_option(self::OPTION_KEY, array());
		$logs = is_array($logs) ? $logs : array();

		$logs[] = array(
			'id'      => $this->generate_log_id(),
			'time'    => current_time('mysql'),
			'level'   => $level,
			'message' => $message,
			'context' => $context,
		);

		if (count($logs) > self::MAX_LOGS) {
			$logs = array_slice($logs, -self::MAX_LOGS);
		}

		update_option(self::OPTION_KEY, $logs, false);
	}

	/**
	 * Get logs.
	 *
	 * @param int $limit Max entries.
	 *
	 * @return array
	 */
	public function get_logs($limit = 50) {
		$limit = max(1, absint($limit));
		$logs  = get_option(self::OPTION_KEY, array());
		$logs  = is_array($logs) ? $logs : array();
		$logs  = $this->normalize_logs($logs);

		if (count($logs) > $limit) {
			$logs = array_slice($logs, -$limit);
		}

		return array_values($logs);
	}

	/**
	 * Delete selected logs by IDs.
	 *
	 * @param array $ids Log IDs.
	 *
	 * @return int Number of removed entries.
	 */
	public function delete_logs_by_ids($ids) {
		$ids = is_array($ids) ? $ids : array();
		$ids = array_map('sanitize_text_field', $ids);
		$ids = array_filter($ids);
		$ids = array_values(array_unique($ids));

		if (empty($ids)) {
			return 0;
		}

		$logs = get_option(self::OPTION_KEY, array());
		$logs = is_array($logs) ? $logs : array();
		$logs = $this->normalize_logs($logs);

		$before = count($logs);
		$logs   = array_values(
			array_filter(
				$logs,
				static function ($entry) use ($ids) {
					if (!is_array($entry) || empty($entry['id'])) {
						return true;
					}

					$log_id = sanitize_text_field((string) $entry['id']);
					return !in_array($log_id, $ids, true);
				}
			)
		);

		$removed = $before - count($logs);
		if ($removed > 0) {
			update_option(self::OPTION_KEY, $logs, false);
		}

		return $removed;
	}

	/**
	 * Get plain text lines for log panel.
	 *
	 * @param int $limit Max entries.
	 *
	 * @return array
	 */
	public function get_recent_lines($limit = 50) {
		$entries = $this->get_logs($limit);
		$lines   = array();

		foreach ($entries as $entry) {
			$time    = !empty($entry['time']) ? sanitize_text_field((string) $entry['time']) : current_time('mysql');
			$level   = !empty($entry['level']) ? sanitize_text_field((string) $entry['level']) : 'INFO';
			$message = !empty($entry['message']) ? sanitize_text_field((string) $entry['message']) : '';
			$context = (!empty($entry['context']) && is_array($entry['context'])) ? $entry['context'] : array();
			$parts   = array();
			if (!empty($context['source'])) {
				$parts[] = 'source:' . sanitize_text_field((string) $context['source']);
			}
			if (!empty($context['generation_method'])) {
				$parts[] = 'method:' . sanitize_text_field((string) $context['generation_method']);
			}
			if (!empty($context['attachment_id'])) {
				$parts[] = 'id:' . absint($context['attachment_id']);
			}
			if (!empty($context['alt_text'])) {
				$parts[] = 'alt:' . sanitize_text_field((string) $context['alt_text']);
			}

			$line = sprintf('[%1$s] [%2$s] %3$s', $time, strtoupper($level), $message);
			if (!empty($parts)) {
				$line .= ' | ' . implode(' | ', $parts);
			}

			$lines[] = $line;
		}

		return $lines;
	}

	/**
	 * Clear stored logs.
	 *
	 * @return void
	 */
	public function clear() {
		update_option(self::OPTION_KEY, array(), false);
	}

	/**
	 * Ensure logs have normalized shape and IDs.
	 *
	 * @param array $logs Raw logs array.
	 *
	 * @return array
	 */
	private function normalize_logs($logs) {
		$logs    = is_array($logs) ? $logs : array();
		$changed = false;
		$output  = array();

		foreach ($logs as $entry) {
			if (!is_array($entry)) {
				$changed = true;
				continue;
			}

			$normalized = $this->normalize_log_entry($entry);
			if ($normalized !== $entry) {
				$changed = true;
			}

			$output[] = $normalized;
		}

		if (count($output) > self::MAX_LOGS) {
			$output  = array_slice($output, -self::MAX_LOGS);
			$changed = true;
		}

		if ($changed) {
			update_option(self::OPTION_KEY, $output, false);
		}

		return $output;
	}

	/**
	 * Normalize one log entry.
	 *
	 * @param array $entry Raw entry.
	 *
	 * @return array
	 */
	private function normalize_log_entry($entry) {
		$entry = is_array($entry) ? $entry : array();
		$id    = !empty($entry['id']) ? sanitize_text_field((string) $entry['id']) : '';
		$time  = !empty($entry['time']) ? sanitize_text_field((string) $entry['time']) : current_time('mysql');
		$level = !empty($entry['level']) ? strtoupper(sanitize_key((string) $entry['level'])) : 'INFO';
		$message = !empty($entry['message']) ? sanitize_text_field((string) $entry['message']) : '';
		$context = !empty($entry['context']) ? $this->sanitize_context($entry['context']) : array();

		if ('' === $id) {
			$id = $this->generate_log_id();
		}

		return array(
			'id'      => $id,
			'time'    => $time,
			'level'   => ('' === $level) ? 'INFO' : $level,
			'message' => $message,
			'context' => $context,
		);
	}

	/**
	 * Generate a unique log ID.
	 *
	 * @return string
	 */
	private function generate_log_id() {
		if (function_exists('wp_generate_uuid4')) {
			return (string) wp_generate_uuid4();
		}

		return sha1(wp_rand() . '|' . microtime(true)); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_sha1
	}

	/**
	 * Sanitize log context payload.
	 *
	 * @param mixed $context Raw context.
	 *
	 * @return array
	 */
	private function sanitize_context($context) {
		if (!is_array($context)) {
			return array();
		}

		$sanitized = array();
		foreach ($context as $key => $value) {
			$sanitized_key = sanitize_key((string) $key);
			if ('' === $sanitized_key) {
				continue;
			}

			if (is_array($value)) {
				$sanitized[$sanitized_key] = $this->sanitize_context($value);
				continue;
			}

			if (is_bool($value)) {
				$sanitized[$sanitized_key] = $value ? 1 : 0;
				continue;
			}

			if (is_numeric($value) && false === strpos((string) $value, '.')) {
				$sanitized[$sanitized_key] = absint($value);
				continue;
			}

			$sanitized[$sanitized_key] = altify_ai_truncate_text(sanitize_text_field((string) $value), 250);
		}

		return $sanitized;
	}
}
