<?php
declare(strict_types=1);
namespace ConfManager\Security;

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


use ConfManager\Traits\Singleton;

/**
 * 監査ログ
 *
 * 全操作を wp_conf_audit_log テーブルに記録する。
 * IPアドレス・User-Agentも自動的に取得して保存。
 *
 * @since 1.0.0
 */
class AuditLog {

	use Singleton;

	private const TABLE = 'conf_audit_log';

	/**
	 * 初期化
	 */
	protected function init(): void {}

	/**
	 * 監査ログを記録する
	 *
	 * @param string   $action      操作名（例: login, update_member, create_payment）
	 * @param string   $target_type 対象種別（例: member, registration, payment）
	 * @param int|null $target_id   対象レコードID
	 * @param array    $detail      操作詳細（JSON化される）
	 * @param int|null $user_id     操作者のWPユーザーID（null=自動取得）
	 */
	public function log(
		string $action,
		string $target_type = '',
		?int $target_id = null,
		array $detail = [],
		?int $user_id = null
	): void {
		global $wpdb;

		$table = $wpdb->prefix . self::TABLE;

		if ( null === $user_id ) {
			$user_id = get_current_user_id() ?: null;
		}

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom table query.
		$wpdb->insert(
			$table,
			[
				'user_id'     => $user_id,
				'action'      => sanitize_text_field( $action ),
				'target_type' => '' !== $target_type ? sanitize_text_field( $target_type ) : null,
				'target_id'   => $target_id,
				'detail_json' => ! empty( $detail ) ? wp_json_encode( $detail, JSON_UNESCAPED_UNICODE ) : null,
				'ip_address'  => $this->get_client_ip(),
				'user_agent'  => $this->get_user_agent(),
			],
			[ '%d', '%s', '%s', '%d', '%s', '%s', '%s' ]
		);
	}

	/**
	 * ログを検索する
	 *
	 * @param array $args {
	 *     検索条件
	 *     @type string $action      操作名で絞り込み
	 *     @type string $target_type 対象種別で絞り込み
	 *     @type int    $target_id   対象IDで絞り込み
	 *     @type int    $user_id     操作者で絞り込み
	 *     @type string $date_from   開始日（YYYY-MM-DD）
	 *     @type string $date_to     終了日（YYYY-MM-DD）
	 *     @type int    $per_page    1ページあたりの件数（デフォルト: 50）
	 *     @type int    $page        ページ番号（デフォルト: 1）
	 * }
	 * @return array{items: array, total: int}
	 */
	public function search( array $args = [] ): array {
		global $wpdb;

		$table    = $wpdb->prefix . self::TABLE;
		$where    = [];
		$values   = [];
		$per_page = absint( $args['per_page'] ?? 50 );
		$page     = max( 1, absint( $args['page'] ?? 1 ) );

		if ( ! empty( $args['action'] ) ) {
			$where[]  = 'action = %s';
			$values[] = sanitize_text_field( $args['action'] );
		}

		if ( ! empty( $args['target_type'] ) ) {
			$where[]  = 'target_type = %s';
			$values[] = sanitize_text_field( $args['target_type'] );
		}

		if ( ! empty( $args['target_id'] ) ) {
			$where[]  = 'target_id = %d';
			$values[] = absint( $args['target_id'] );
		}

		if ( ! empty( $args['user_id'] ) ) {
			$where[]  = 'user_id = %d';
			$values[] = absint( $args['user_id'] );
		}

		if ( ! empty( $args['date_from'] ) ) {
			$where[]  = 'created_at >= %s';
			$values[] = sanitize_text_field( $args['date_from'] ) . ' 00:00:00';
		}

		if ( ! empty( $args['date_to'] ) ) {
			$where[]  = 'created_at <= %s';
			$values[] = sanitize_text_field( $args['date_to'] ) . ' 23:59:59';
		}

		$where_sql = ! empty( $where ) ? 'WHERE ' . implode( ' AND ', $where ) : '';
		$offset    = ( $page - 1 ) * $per_page;

		// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Hardcoded WP prefix table; $where_sql uses validated placeholders; values pass through $wpdb->prepare().
		// 総件数
		$count_sql = "SELECT COUNT(*) FROM {$table} {$where_sql}";
		$total     = ! empty( $values )
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Dynamic query built with safe values
			? (int) $wpdb->get_var( $wpdb->prepare( $count_sql, ...$values ) )
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Dynamic query built with safe values
			: (int) $wpdb->get_var( $count_sql );

		// データ取得
		$query_values   = array_merge( $values, [ $per_page, $offset ] );
		$data_sql       = "SELECT * FROM {$table} {$where_sql} ORDER BY created_at DESC LIMIT %d OFFSET %d";
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Dynamic query built with safe values
		$items          = $wpdb->get_results(
			$wpdb->prepare( $data_sql, ...$query_values ), // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
			ARRAY_A
		);
		// phpcs:enable WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

		return [
			'items' => $items,
			'total' => $total,
		];
	}

	/**
	 * 古いログを削除する（保持期間超過分）
	 *
	 * @param int $days 保持日数（デフォルト: 365日）
	 * @return int 削除された件数
	 */
	public function purge( int $days = 365 ): int {
		global $wpdb;

		$table    = $wpdb->prefix . self::TABLE;
		$cutoff   = gmdate( 'Y-m-d H:i:s', time() - ( $days * DAY_IN_SECONDS ) );

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table purge; no caching applicable.
		return (int) $wpdb->query(
			$wpdb->prepare(
				"DELETE FROM {$table} WHERE created_at < %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from plugin-defined prefix.
				$cutoff
			)
		);
	}

	/**
	 * クライアントIPアドレスを取得
	 *
	 * @return string|null
	 */
	private function get_client_ip(): ?string {
		$ip = RateLimiter::instance()->get_client_ip();
		return '0.0.0.0' === $ip ? null : $ip;
	}

	/**
	 * User-Agentを取得
	 *
	 * @return string|null
	 */
	private function get_user_agent(): ?string {
		if ( ! isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
			return null;
		}
		$ua = sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- Sanitized on this line.
		if ( '' === $ua ) {
			return null;
		}
		return substr( $ua, 0, 500 );
	}
}
