<?php
/**
 * Append-only audit log for the magic-link lifecycle.
 *
 * The Logger writes to `{prefix}kc_ml_audit_log`. Each row is a single
 * lifecycle event (generated, sent, opened, used, expired, revoked,
 * failed, delivered, bounced). The table is intentionally narrow so
 * inserts stay cheap even on busy sites.
 *
 * @package KaizenCoders\MagicLink\AuditLog
 */

namespace KaizenCoders\MagicLink\AuditLog;

use KaizenCoders\MagicLink\Helper;

class Logger {

	const TABLE = 'kc_ml_audit_log';

	/**
	 * Fully-qualified table name.
	 *
	 * @return string
	 */
	public static function table() {
		global $wpdb;
		return $wpdb->prefix . self::TABLE;
	}

	/**
	 * Record one event.
	 *
	 * @param  string $event   Event type (use Events::* constants).
	 * @param  array  $context Optional context: user_id, link_id, ip, ua, email, country, meta (array).
	 *
	 * @return int|false  Insert id on success, false on failure.
	 */
	public static function record( $event, $context = array() ) {
		if ( ! in_array( $event, Events::all(), true ) ) {
			return false;
		}

		$context = self::normalize_context( $context );

		do_action( 'kc_ml_audit_log_before_record', $event, $context );

		if ( ! isset( $GLOBALS['wpdb'] ) || ! is_object( $GLOBALS['wpdb'] ) ) {
			return false;
		}

		global $wpdb;
		$inserted = $wpdb->insert(
			self::table(),
			array(
				'event_type' => $event,
				'user_id'    => (int) $context['user_id'],
				'link_id'    => (int) $context['link_id'],
				'email'      => $context['email'],
				'ip_address' => $context['ip'],
				'user_agent' => $context['ua'],
				'country'    => $context['country'],
				'meta_json'  => wp_json_encode( $context['meta'] ),
				'created_at' => $context['created_at'],
			),
			array( '%s', '%d', '%d', '%s', '%s', '%s', '%s', '%s', '%s' )
		);

		if ( ! $inserted ) {
			return false;
		}

		$id = (int) $wpdb->insert_id;
		do_action( 'kc_ml_audit_log_recorded', $id, $event, $context );

		return $id;
	}

	/**
	 * Normalise a context array.
	 *
	 * @param  array $context Caller-supplied context.
	 *
	 * @return array
	 */
	public static function normalize_context( $context ) {
		$defaults = array(
			'user_id'    => 0,
			'link_id'    => 0,
			'email'      => '',
			'ip'         => '',
			'ua'         => '',
			'country'    => '',
			'meta'       => array(),
			'created_at' => '',
		);
		$context  = array_merge( $defaults, (array) $context );

		if ( '' === $context['ip'] && class_exists( '\\KaizenCoders\\MagicLink\\Helper' ) ) {
			$ip = Helper::get_ip();
			if ( ! is_array( $ip ) ) {
				$context['ip'] = (string) $ip;
			}
		}
		if ( '' === $context['ua'] ) {
			$context['ua'] = isset( $_SERVER['HTTP_USER_AGENT'] ) ? substr( (string) $_SERVER['HTTP_USER_AGENT'], 0, 255 ) : '';
		}
		if ( '' === $context['created_at'] ) {
			$context['created_at'] = gmdate( 'Y-m-d H:i:s' );
		}
		if ( ! is_array( $context['meta'] ) ) {
			$context['meta'] = array( 'value' => $context['meta'] );
		}

		return $context;
	}

	/**
	 * Query the log.
	 *
	 * @param  array $args event_type, user_id, since, until, per_page, page, search.
	 *
	 * @return array
	 */
	public static function query( $args = array() ) {
		if ( ! isset( $GLOBALS['wpdb'] ) || ! is_object( $GLOBALS['wpdb'] ) ) {
			return array();
		}
		global $wpdb;

		$args = array_merge(
			array(
				'event_type' => '',
				'user_id'    => 0,
				'since'      => '',
				'until'      => '',
				'per_page'   => 50,
				'page'       => 1,
				'search'     => '',
			),
			(array) $args
		);

		$where   = array( '1=1' );
		$params  = array();
		$builder = self::build_where( $args );
		$where   = array_merge( $where, $builder['where'] );
		$params  = array_merge( $params, $builder['params'] );

		$per_page = max( 1, (int) $args['per_page'] );
		$page     = max( 1, (int) $args['page'] );
		$offset   = ( $page - 1 ) * $per_page;

		$sql = 'SELECT * FROM ' . self::table()
			. ' WHERE ' . implode( ' AND ', $where )
			. ' ORDER BY id DESC LIMIT %d OFFSET %d';

		$params[] = $per_page;
		$params[] = $offset;

		return (array) $wpdb->get_results( $wpdb->prepare( $sql, $params ), ARRAY_A );
	}

	/**
	 * Count rows matching the same filters used by query().
	 *
	 * @param  array $args See query().
	 *
	 * @return int
	 */
	public static function count( $args = array() ) {
		if ( ! isset( $GLOBALS['wpdb'] ) || ! is_object( $GLOBALS['wpdb'] ) ) {
			return 0;
		}
		global $wpdb;

		$args = (array) $args;

		$where   = array( '1=1' );
		$builder = self::build_where( $args );
		$where   = array_merge( $where, $builder['where'] );
		$params  = $builder['params'];

		$sql = 'SELECT COUNT(*) FROM ' . self::table() . ' WHERE ' . implode( ' AND ', $where );
		if ( empty( $params ) ) {
			return (int) $wpdb->get_var( $sql );
		}
		return (int) $wpdb->get_var( $wpdb->prepare( $sql, $params ) );
	}

	/**
	 * Convert a result set to a CSV string. Used by the admin export
	 * endpoint and unit tests alike.
	 *
	 * @param  array $rows Rows from query().
	 *
	 * @return string
	 */
	public static function to_csv( $rows ) {
		$columns = array( 'id', 'created_at', 'event_type', 'user_id', 'link_id', 'email', 'ip_address', 'country', 'user_agent', 'meta_json' );

		$fh = fopen( 'php://temp', 'w+' );
		fputcsv( $fh, $columns, ',', '"', '\\' );

		foreach ( (array) $rows as $row ) {
			$out = array();
			foreach ( $columns as $col ) {
				$out[] = isset( $row[ $col ] ) ? (string) $row[ $col ] : '';
			}
			fputcsv( $fh, $out, ',', '"', '\\' );
		}

		rewind( $fh );
		$csv = stream_get_contents( $fh );
		fclose( $fh );

		return $csv;
	}

	/**
	 * Build WHERE clause fragments + params from query args.
	 *
	 * @param  array $args Query args.
	 *
	 * @return array{where: string[], params: array}
	 */
	private static function build_where( $args ) {
		$where  = array();
		$params = array();

		if ( ! empty( $args['event_type'] ) ) {
			$where[]  = 'event_type = %s';
			$params[] = (string) $args['event_type'];
		}
		if ( ! empty( $args['user_id'] ) ) {
			$where[]  = 'user_id = %d';
			$params[] = (int) $args['user_id'];
		}
		if ( ! empty( $args['since'] ) ) {
			$where[]  = 'created_at >= %s';
			$params[] = (string) $args['since'];
		}
		if ( ! empty( $args['until'] ) ) {
			$where[]  = 'created_at <= %s';
			$params[] = (string) $args['until'];
		}
		if ( ! empty( $args['search'] ) ) {
			$where[]  = '(email LIKE %s OR ip_address LIKE %s OR meta_json LIKE %s)';
			$like     = '%' . $args['search'] . '%';
			$params[] = $like;
			$params[] = $like;
			$params[] = $like;
		}

		return array(
			'where'  => $where,
			'params' => $params,
		);
	}
}
