<?php
/**
 * BookiFlex Payment Extension
 *
 * @package    BookiFlex
 * @subpackage Payment
 * @author     BookiFlex
 * @copyright  Copyright (c) 2025 BookiFlex
 * @license    GPL-2.0-or-later
 *
 * @link       https://bookiflex.com
 */

declare(strict_types=1);

namespace BookiFlex\Extensions\Payment\Repository;

use InvalidArgumentException;

defined( 'ABSPATH' ) || exit;

/**
 * Data Transfer Object for payment list filtering and pagination.
 *
 * Encapsulates all filter parameters for querying payments in admin table list.
 * Immutable (readonly) to prevent accidental mutations during query building.
 *
 * @package BookiFlex\Extensions\Payment\Repository
 */
class PaymentQueryFilter
{
    /**
     * Allowed orderBy fields for sorting
     */
    private const ALLOWED_ORDER_BY = [
        'id',
        'customer',
        'total_amount',
        'status',
        'gateway_name',
        'created_at',
        'updated_at',
    ];

    /**
     * Create payment query filter.
     *
     * @param string|null $status Filter by payment status (e.g., 'waiting', 'paid')
     * @param int|null $customerId Filter by customer ID
     * @param string|null $searchTerm Search term for customer name or email
     * @param string $orderBy Field to sort by (must be in ALLOWED_ORDER_BY)
     * @param string $order Sort direction (ASC or DESC)
     * @param int $limit Number of results per page
     * @param int $offset Offset for pagination
     *
     * @throws InvalidArgumentException If validation fails
     */
    public function __construct(
        public ?string $status = null,
        public ?int $customerId = null,
        public ?string $searchTerm = null,
        public string $orderBy = 'created_at',
        public string $order = 'DESC',
        public int $limit = 10,
        public int $offset = 0,
    ) {
        // Validate order direction
        $this->order = strtoupper($this->order);
        if (!in_array($this->order, ['ASC', 'DESC'], true)) {
            throw new InvalidArgumentException(
                sprintf('Order must be ASC or DESC, got: %s', $order)
            );
        }

        // Validate orderBy field
        if (!in_array($this->orderBy, self::ALLOWED_ORDER_BY, true)) {
            throw new InvalidArgumentException(
                sprintf(
                    'Invalid orderBy field: %s. Allowed: %s',
                    $this->orderBy,
                    implode(', ', self::ALLOWED_ORDER_BY)
                )
            );
        }

        // Validate numeric constraints
        if ($this->limit < 0) {
            throw new InvalidArgumentException('Limit must be non-negative');
        }

        if ($this->offset < 0) {
            throw new InvalidArgumentException('Offset must be non-negative');
        }

        if ($this->customerId !== null && $this->customerId <= 0) {
            throw new InvalidArgumentException('Customer ID must be positive');
        }

        // Trim string values
        if ($this->searchTerm !== null) {
            $this->searchTerm = trim($this->searchTerm);
            if ($this->searchTerm === '') {
                $this->searchTerm = null;
            }
        }

        if ($this->status !== null) {
            $this->status = trim($this->status);
            if ($this->status === '') {
                $this->status = null;
            }
        }
    }

    /**
     * Create filter from HTTP request parameters.
     *
     * Factory method for creating filter from WordPress admin request.
     * Handles sanitization and type conversion of request parameters.
     *
     * @param array $request Request parameters (typically $_GET + $_REQUEST merged)
     * @param int $perPage Number of items per page
     * @param int $currentPage Current page number (1-indexed)
     *
     * @return self
     *
     * @example
     * ```php
     * $filter = PaymentQueryFilter::fromRequest(
     *     array_merge($_GET, $_REQUEST),
     *     20,  // per page
     *     2    // page 2
     * );
     * ```
     */
    public static function fromRequest(array $request, int $perPage, int $currentPage): self
    {
        return new self(
            status: !empty($request['status']) ? (string) $request['status'] : null,
            customerId: !empty($request['client_id']) && is_numeric($request['client_id'])
                ? (int) $request['client_id']
                : null,
            searchTerm: !empty($request['s']) ? (string) $request['s'] : null,
            orderBy: !empty($request['orderby']) ? (string) $request['orderby'] : 'created_at',
            order: !empty($request['order']) ? strtoupper((string) $request['order']) : 'DESC',
            limit: $perPage,
            offset: max(0, ($currentPage - 1) * $perPage),
        );
    }

    /**
     * Check if any filters are active (excluding pagination/sorting).
     *
     * @return bool True if at least one filter is set
     */
    public function hasFilters(): bool
    {
        return $this->status !== null
            || $this->customerId !== null
            || $this->searchTerm !== null;
    }

    /**
     * Create a copy of this filter without status filter.
     *
     * Useful for getting status counts across all statuses.
     *
     * @return self New filter instance without status
     */
    public function withoutStatus(): self
    {
        return new self(
            status: null,
            customerId: $this->customerId,
            searchTerm: $this->searchTerm,
            orderBy: $this->orderBy,
            order: $this->order,
            limit: $this->limit,
            offset: $this->offset,
        );
    }

    /**
     * Create a copy of this filter without pagination.
     *
     * Useful for counting total results or exporting all data.
     *
     * @return self New filter instance with limit=0, offset=0
     */
    public function withoutPagination(): self
    {
        return new self(
            status: $this->status,
            customerId: $this->customerId,
            searchTerm: $this->searchTerm,
            orderBy: $this->orderBy,
            order: $this->order,
            limit: 0,
            offset: 0,
        );
    }
}
