<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
/**
 * Payout Schedule Logs Database Abstraction Layer
 *
 * @package     AffiliateWP
 * @subpackage  Database
 * @copyright   Copyright (c) 2026, Awesome Motive, Inc
 * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
 * @since       2.35.0
 */

#[AllowDynamicProperties]

/**
 * Core class that implements a database layer for payout schedule logs.
 *
 * @since 2.35.0
 *
 * @see \Affiliate_WP_DB
 */
class Affiliate_WP_Payout_Schedule_Logs_DB extends Affiliate_WP_DB {

	/**
	 * Cache group for queries.
	 *
	 * @since 2.35.0
	 * @var   string
	 */
	public $cache_group = 'payout_schedule_logs';

	/**
	 * Database group value.
	 *
	 * @since 2.35.0
	 * @var   string
	 */
	public $db_group = 'affiliates:payout_schedule_logs';

	/**
	 * Object type to query for.
	 *
	 * @since 2.35.0
	 * @var   string
	 */
	public $query_object_type = 'AffWP\Affiliate\PayoutScheduleLog';

	/**
	 * Constructor.
	 *
	 * @since 2.35.0
	 */
	public function __construct() {
		global $wpdb;

		if ( defined( 'AFFILIATE_WP_NETWORK_WIDE' ) && AFFILIATE_WP_NETWORK_WIDE ) {
			$this->table_name = 'affiliate_wp_payout_schedule_logs';
		} else {
			$this->table_name = $wpdb->prefix . 'affiliate_wp_payout_schedule_logs';
		}

		$this->primary_key = 'log_id';
		$this->version     = '1.0';
	}

	/**
	 * Retrieves a payout schedule log object.
	 *
	 * @since 2.35.0
	 *
	 * @param int|object $log Log ID or object.
	 * @return AffWP\Affiliate\PayoutScheduleLog|false Log object, false otherwise.
	 */
	public function get_object( $log ) {
		return $this->get_core_object( $log, $this->query_object_type );
	}

	/**
	 * Retrieves table columns and data types.
	 *
	 * @since 2.35.0
	 *
	 * @return array Column name => format pairs.
	 */
	public function get_columns() {
		return array(
			'log_id'           => '%d',
			'schedule_id'      => '%d',
			'batch_id'         => '%d',
			'run_at'           => '%s',
			'status'           => '%s',
			'errors'           => '%s',
		);
	}

	/**
	 * Retrieves default column values.
	 *
	 * @since 2.35.0
	 *
	 * @return array Default column values.
	 */
	public function get_column_defaults() {
		return array(
			'schedule_id'      => 0,
			'batch_id'         => 0,
			'run_at'           => gmdate( 'Y-m-d H:i:s' ),
			'status'           => 'processing',
			'errors'           => '',
		);
	}

	/**
	 * Adds a new log entry.
	 *
	 * @since 2.35.0
	 *
	 * @param array $args Log entry arguments.
	 * @return int|false Log ID if successfully added, otherwise false.
	 */
	public function add( $args = array() ) {

		$args = wp_parse_args( $args, array(
			'schedule_id'      => 0,
			'batch_id'         => 0,
			'run_at'           => gmdate( 'Y-m-d H:i:s' ),
			'status'           => 'processing',
			'errors'           => '',
		) );

		if ( empty( $args['schedule_id'] ) ) {
			return false;
		}

		$args['schedule_id'] = absint( $args['schedule_id'] );

		// A log row is born 'processing' — an outcome status is only ever set explicitly.
		$valid_statuses = array( 'processing', 'success', 'partial', 'failed', 'skipped' );
		if ( ! in_array( $args['status'], $valid_statuses, true ) ) {
			$args['status'] = 'processing';
		}

		if ( ! empty( $args['errors'] ) && is_array( $args['errors'] ) ) {
			$args['errors'] = wp_json_encode( $args['errors'] );
		}

		$add = $this->insert( $args, 'payout_schedule_log' );

		if ( $add ) {
			/**
			 * Fires after a payout schedule log has been inserted.
			 *
			 * @since 2.35.0
			 *
			 * @param int $add New log ID.
			 */
			do_action( 'affwp_insert_payout_schedule_log', $add );

			return $add;
		}

		return false;
	}

	/**
	 * Retrieves logs from the database.
	 *
	 * @since 2.35.0
	 *
	 * @param array $args Query arguments.
	 * @param bool  $count Whether to return only count.
	 * @return array|int Array of log objects or count.
	 */
	public function get_logs( $args = array(), $count = false ) {
		global $wpdb;

		$defaults = array(
			'number'      => 20,
			'offset'      => 0,
			'log_id'      => 0,
			'schedule_id' => 0,
			'status'      => '',
			'order'       => 'DESC',
			'orderby'     => 'log_id',
			'fields'      => '',
		);

		$args = wp_parse_args( $args, $defaults );

		if ( $args['number'] < 1 ) {
			$args['number'] = 999999999999;
		}

		$where = $join = '';

		// Specific logs.
		if ( ! empty( $args['log_id'] ) ) {
			$where .= empty( $where ) ? "WHERE " : "AND ";

			if ( is_array( $args['log_id'] ) ) {
				$log_ids = implode( ',', array_map( 'intval', $args['log_id'] ) );
			} else {
				$log_ids = intval( $args['log_id'] );
			}

			$log_ids = esc_sql( $log_ids );
			$where .= "`log_id` IN( {$log_ids} ) ";
		}

		// Schedule ID.
		if ( ! empty( $args['schedule_id'] ) ) {
			$where .= empty( $where ) ? "WHERE " : "AND ";
			$schedule_id = intval( $args['schedule_id'] );
			$where .= "`schedule_id` = {$schedule_id} ";
		}

		// Status.
		if ( ! empty( $args['status'] ) ) {
			$valid_statuses = array( 'processing', 'success', 'partial', 'failed', 'skipped' );

			if ( in_array( $args['status'], $valid_statuses, true ) ) {
				$where .= empty( $where ) ? "WHERE " : "AND ";
				$where .= "`status` = '" . esc_sql( $args['status'] ) . "' ";
			}
		}

		// Select valid logs only.
		$where .= empty( $where ) ? "WHERE " : "AND ";
		$where .= "`$this->primary_key` > 0";

		$orderby = array_key_exists( $args['orderby'], $this->get_columns() ) ? $args['orderby'] : $this->primary_key;

		if ( 'DESC' === strtoupper( $args['order'] ) ) {
			$order = 'DESC';
		} else {
			$order = 'ASC';
		}

		$args['orderby'] = $orderby;
		$args['order']   = $order;

		$callback = '';

		if ( 'ids' === $args['fields'] ) {
			$fields   = "$this->primary_key";
			$callback = 'intval';
		} else {
			$fields = $this->parse_fields( $args['fields'] );

			if ( '*' === $fields ) {
				$callback = 'affwp_get_payout_schedule_log';
			}
		}

		$key = ( true === $count ) ? md5( 'affwp_payout_schedule_logs_count' . serialize( $args ) ) : md5( 'affwp_payout_schedule_logs_' . serialize( $args ) );

		$last_changed = wp_cache_get( 'last_changed', $this->cache_group );
		if ( ! $last_changed ) {
			$last_changed = microtime();
			wp_cache_set( 'last_changed', $last_changed, $this->cache_group );
		}

		$cache_key = "{$key}:{$last_changed}";
		$results   = wp_cache_get( $cache_key, $this->cache_group );

		if ( false === $results ) {
			$clauses = compact( 'fields', 'join', 'where', 'orderby', 'order', 'count' );
			$results = $this->get_results( $clauses, $args, $callback );
		}

		wp_cache_add( $cache_key, $results, $this->cache_group, HOUR_IN_SECONDS );

		return $results;
	}

	/**
	 * Retrieves the count for a given query.
	 *
	 * @since 2.35.0
	 *
	 * @param array $args Arguments.
	 * @return int Count.
	 */
	public function count( $args = array() ) {
		return $this->get_logs( $args, true );
	}

	/**
	 * Creates the table.
	 *
	 * @since 2.35.0
	 */
	public function create_table() {
		require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );

		$sql = "CREATE TABLE " . $this->table_name . " (
			log_id bigint(20) NOT NULL AUTO_INCREMENT,
			schedule_id bigint(20) NOT NULL,
			batch_id bigint(20) NOT NULL DEFAULT 0,
			run_at datetime NOT NULL,
			status varchar(20) NOT NULL DEFAULT 'processing',
			errors longtext DEFAULT NULL,
			PRIMARY KEY  (log_id),
			KEY schedule_id (schedule_id),
			KEY batch_id (batch_id),
			KEY status (status),
			KEY run_at (run_at)
			) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;";

		dbDelta( $sql );

		update_option( $this->table_name . '_db_version', $this->version );
	}
}
