<?php namespace Tussendoor\Reloadify\Http\Filters;

use ReloadifyScoped\Carbon\Carbon;
use ReloadifyScoped\Carbon\Exceptions\InvalidFormatException;
use ReloadifyScoped\Illuminate\Database\Eloquent\Builder;
use Tussendoor\Reloadify\Exceptions\FilterException;
use WP_REST_Request;

abstract class Filter
{
    /**
     * The request instance.
     *
     * @var \WP_REST_Request
     */
    protected $request;

    /**
     * The builder instance.
     *
     * @var \Illuminate\Database\Eloquent\Builder
     */
    protected $builder;

    /**
     * The builder operators
     *
     * @var array
     */
    protected $operators = [
        'eq'    => '=',
        'lt'    => '<',
        'gt'    => '>',
        'to'    => '<=',
        'from'  => '>=',
        'lteq'  => '<=',
        'gteq'  => '>=',
        'in'    => 'in',
        'nin'   => 'nin',
        'asc'   => 'asc',
        'desc'  => 'desc',
    ];

    /**
     * The accepted date operators
     *
     * @var array
     */
    protected $acceptedDateOperators = [
        'to',
        'from',
    ];

    /**
     * The accepted string operators
     *
     * @var array
     */
    protected $acceptedStringOperators = [
        'eq',
    ];

    /**
     * The accepted product filter operators
     *
     * @var array
     */
    protected $acceptedProductOperators = [
        'in',
        'nin',
    ];

    /**
     * The accepted sorting operators
     *
     * @var array
     */
    protected $acceptedSortOperators = [
        'asc',
        'desc',
    ];

    /**
     * The accepted toplevel filters
     * Split by default and optional filters
     *
     * @var array
     */
    protected $topLevelFilters = [
        'default' => [
            'pagination',
        ],
        'optional' => [
            'after'
        ],
    ];

    /**
     * Initialize a new filter instance.
     *
     * @param  \WP_REST_Request  $request
     * @return void
     */
    public function __construct(WP_REST_Request $request)
    {
        $this->request = $request;
    }

    /**
     * Apply the filters on the builder.
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $builder
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function apply(Builder $builder) : Builder
    {
        $this->builder = $builder;

        if ($this->request->has_param('filters')) {
            foreach ($this->request->get_param('filters') as $attribute => $data) {
                if (method_exists($this, $attribute)) {
                    call_user_func_array([$this, $attribute], array_filter([$data]));
                }
            }
        }

        $this->addTopLevelFilters();

        return $this->builder;
    }

    /**
     * Execute default and optional filters
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function addTopLevelFilters()
    {

        foreach ($this->topLevelFilters as $type => $filters) {

            if ($type === 'default') {
                foreach ($filters as $filter) {
                    if (method_exists($this, $filter)) {
                        call_user_func_array([$this, $filter], []);
                    }
                }
            }

            if ($type === 'optional') {
                foreach ($filters as $filter) {

                    if (!$this->request->has_param($filter)) continue;

                    if (method_exists($this, $filter)) {
                        call_user_func_array([$this, $filter], [$this->request->get_param($filter)]);
                    }

                }
            }
        }

        return $this->builder;
    }

    /**
     * Add default pagination of 10 when not overwritten in WP_REST_Request
     * Add default page of 1 when not overwritten in WP_REST_Request
     *
     * @example wp-json/wc/v3/reloadify/shopping_carts?page=2&per_page=5
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function pagination()
    {
        $perPage    = $this->request->has_param('per_page') ? $this->request->get_param('per_page') : 10;
        $page       = $this->request->has_param('page') ? $this->request->get_param('page') : 1;

        return $this->builder->paginate($perPage, ['*'], 'page', $page);
    }

    /**
     * Add optional toplevel filter to execute when parameter is set.
     *
     * @example wp-json/wc/v3/reloadify/shopping_carts?after=2022-03-03
     *
     * @param  string $value
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function after(string $parameter)
    {
        // Check if the parameter ends with a space followed by a timezone
        // offset pattern, e.g., "02:00" If so replace the space back to a +
        if (preg_match('/\s(\d{2}:\d{2})$/', $parameter)) {
            $parameter = str_replace(' ', '+', $parameter);
        }

        try {
            $value = Carbon::parse($parameter);
        } catch (InvalidFormatException $e) {
            throw new FilterException('Given date could not be parsed: \'' . $parameter . '\'', 400);
        }

        return $this->builder->where('created_at_gmt', '>', $value->timezone('GMT')->toDateTimeString());
    }
}
