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

declare(strict_types=1);

namespace BookiFlex\Core\Api\Request;

use WP_REST_Request;

defined( 'ABSPATH' ) || exit;

abstract class AbstractRequest
{
    /**
     * Create request instance from WP REST Request
     */
    abstract public static function fromWpRestRequest(WP_REST_Request $request): self;

    /**
     * Get validation rules for WP REST API
     */
    abstract public static function getValidationRules(): array;

    /**
     * Sanitize string value
     */
    public static function sanitizeString(string|int|float|null $value): string
    {
        if (is_numeric($value)) {
            $value = (string) $value;
        }

        return sanitize_text_field($value ?? '');
    }

    /**
     * Sanitize integer value
     */
    public static function sanitizeInt(mixed $value): int
    {
        return absint($value);
    }

    /**
     * Sanitize boolean value
     */
    public static function sanitizeBool(mixed $value): bool
    {
        return (bool) $value;
    }

    /**
     * Validate date format (Y-m-d)
     */
    public static function isValidDate(mixed $value): bool
    {
        if (!is_string($value)) {
            return false;
        }

        return (bool) preg_match('/^\d{4}-\d{2}-\d{2}$/', $value);
    }

    /**
     * Validate positive integer
     */
    public static function isPositiveInt(mixed $value): bool
    {
        return is_numeric($value) && (int) $value > 0;
    }

    /**
     * Create date validation rule
     */
    protected static function dateRule(bool $required = true): array
    {
        return [
            'required' => $required,
            'validate_callback' => [static::class, 'isValidDate'],
            'sanitize_callback' => [static::class, 'sanitizeString'],
        ];
    }

    /**
     * Create positive integer validation rule
     */
    protected static function positiveIntRule(bool $required = true): array
    {
        return [
            'required' => $required,
            'validate_callback' => [static::class, 'isPositiveInt'],
            'sanitize_callback' => [static::class, 'sanitizeInt'],
        ];
    }

    /**
     * Create array validation rule
     */
    protected static function arrayRule(bool $required = true, mixed $default = null): array
    {
        $rule = [
            'required' => $required,
            'validate_callback' => fn($value) => is_array($value),
        ];

        if (!$required && $default !== null) {
            $rule['default'] = $default;
        }

        return $rule;
    }

    /**
     * Create date range validation rule
     *
     * @param string $fromField Name of the "from" date field
     * @param string $toField Name of the "to" date field
     * @param bool $allowEqual Allow dates to be equal (true: <=, false: <)
     * @param bool $required Whether the field is required
     */
    protected static function dateRangeRule(
        string $fromField,
        string $toField,
        bool $allowEqual = true,
        bool $required = true
    ): array {
        return [
            'required' => $required,
            'validate_callback' => function ($value, $request, $param) use ($fromField, $toField, $allowEqual) {
                // First validate date format
                if (!self::isValidDate($value)) {
                    return false;
                }

                // Get all parameters
                $params = $request->get_json_params() ?? $request->get_params();

                // Check if both fields exist
                if (!isset($params[$fromField]) || !isset($params[$toField])) {
                    return true; // Skip range validation if fields are missing
                }

                $dateFrom = $params[$fromField];
                $dateTo = $params[$toField];

                // Validate both dates
                if (!self::isValidDate($dateFrom) || !self::isValidDate($dateTo)) {
                    return true; // Individual field validation will catch this
                }

                // Perform range validation
                if ($allowEqual) {
                    if ($dateFrom > $dateTo) {
                        return new \WP_Error(
                            'invalid_date_range',
                            sprintf(
                                'Field %s must be less than or equal to %s',
                                $fromField,
                                $toField
                            )
                        );
                    }
                } else {
                    if ($dateFrom >= $dateTo) {
                        return new \WP_Error(
                            'invalid_date_range',
                            sprintf(
                                'Field %s must be strictly less than %s',
                                $fromField,
                                $toField
                            )
                        );
                    }
                }

                return true;
            },
            'sanitize_callback' => [static::class, 'sanitizeString'],
        ];
    }

    /**
     * Create max nights validation rule for checkout date
     *
     * @param string $fromField Name of the check-in date field
     * @param int $maxNights Maximum allowed nights
     * @param bool $required Whether the field is required
     */
    protected static function maxNightsRule(
        string $fromField,
        int $maxNights,
        bool $required = true
    ): array {
        return [
            'required' => $required,
            'validate_callback' => function ($value, $request, $param) use ($fromField, $maxNights) {
                // First validate date format
                if (!self::isValidDate($value)) {
                    return false;
                }

                $params = $request->get_json_params() ?? $request->get_params();

                if (!isset($params[$fromField]) || !self::isValidDate($params[$fromField])) {
                    return true; // Skip if from field missing/invalid
                }

                $dateFrom = new \DateTimeImmutable($params[$fromField]);
                $dateTo = new \DateTimeImmutable($value);
                $nights = (int) $dateFrom->diff($dateTo)->days;

                if ($nights > $maxNights) {
                    return new \WP_Error(
                        'max_nights_exceeded',
                        sprintf(
                            /* translators: %d: maximum number of nights */
                            __('Maximum stay is %d nights', 'bookiflex'),
                            $maxNights
                        )
                    );
                }

                return true;
            },
            'sanitize_callback' => [static::class, 'sanitizeString'],
        ];
    }
}
