<?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;

class AbandonedCartFilter extends Filter
{
    /**
     * Filter the carts by the given first_name.
     *
     * @example /wp-json/wc/v3/reloadify/shopping_carts?filters[first_name][eq]=Wim
     *
     * @param array $data
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function first_name(array $data = []) : Builder
    {
        return $this->handleMetaFilter($data, 'first_name');
    }

    /**
     * Filter the carts by the given last_name.
     *
     * @example /wp-json/wc/v3/reloadify/shopping_carts?filters[last_name][eq]=Braam
     *
     * @param array $data
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function last_name(array $data = []) : Builder
    {
        return $this->handleMetaFilter($data, 'last_name');
    }

    /**
     * Handle a filter based on a meta_key
     *
     * @param array $data
     * @param string $metaKey
     * @return \Illuminate\Database\Eloquent\Builder
     */
    private function handleMetaFilter(array $data,  string $metaKey) : Builder
    {
        foreach ($data as $operator => $value) {

            if (! in_array($operator, $this->acceptedStringOperators)) {
                throw new FilterException('Unaccepted operator for string type. Accepted types: ' . implode(', ', $this->acceptedDateOperators), 400);
            }

            $this->builder->whereHas('meta', function($query) use($metaKey, $operator, $value) {
                $query->where('meta_key', $metaKey)->where('meta_value', $this->operators[$operator], $value);
            });
        }

        return $this->builder;
    }

    /**
     * Filter the carts by the given price.
     *
     * @example /wp-json/wc/v3/reloadify/shopping_carts?filters[price][gt]=31.10&filters[price][lt]=100
     *
     * @param array $data
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function price(array $data = []) : Builder
    {
        foreach ($data as $operator => $value) {
            $this->builder->where('price', $this->operators[$operator], $value);
        }
        return $this->builder;
    }

    /**
     * Filter the carts by the given currency.
     *
     * @example /wp-json/wc/v3/reloadify/shopping_carts?filters[currency][eq]=Euro
     *
     * @param array $data
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function currency(array $data = []) : Builder
    {
        foreach ($data as $operator => $value) {
            $this->builder->where('currency', $this->operators[$operator], $value);
        }
        return $this->builder;
    }

    /**
     * Filter the carts by the given product id.
     *
     * @param array $data
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function product(array $data = []) : Builder
    {
        if (! array_key_exists('in', $data) && ! array_key_exists('nin', $data)) {
            throw new FilterException('Accepted operators for product filter not found. Accepted types: ' . implode(', ', $this->acceptedProductOperators), 400);
        }

        /**
         * Search for enties with products that contain given ID
         * @example filters[product][in][]=26&filters[product][in][]=21
         */
        if (!empty($data['in'])) {
            foreach ($data['in'] as $id) {
                $searchArray = [
                    (int) $id
                ];
                $this->builder->orWhereJsonContains('product_ids', $searchArray);
            }
        }

        /**
         * Search only for entries whith products that do not contain given ID
         * @example filters[product][in][]=18&filters[product][nin][]=26
         */
        if (!empty($data['nin'])) {
            foreach ($data['nin'] as $id) {
                $searchArray = [
                    (int) $id
                ];
                $this->builder->whereJsonDoesntContain('product_ids', $searchArray);
            }
        }

        return $this->builder;
    }

    /**
     * Filter the carts by the given created_at date
     *
     * @param array $data
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function created_at(array $data = []) : Builder
    {
        return $this->handleDateFilter($data, 'created_at');
    }

    /**
     * Filter the carts by the given updated_at date
     *
     * @param array $data
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function updated_at(array $data = []) : Builder
    {
        return $this->handleDateFilter($data, 'updated_at');
    }

    /**
     * Filter the carts by the given date
     *
     * @param array $data
     * @param string $field
     * @return \Illuminate\Database\Eloquent\Builder
     */
    private function handleDateFilter(array $data, string $field)
    {

        /**
         * Do not combine optional toplevel filter "after" with the date filter.
         */
        if ($this->request->has_param('after')) {
            throw new FilterException('Optional toplevel filter \'after\' can not be combined with the date filter. Please only use \'after\' or the date filter like \'filters[created_at][to]=2022-02-04\'.', 400);
        }

        /**
         * If both "to" and "from" dont exist throw error as soon as possible
         */
        if (! array_key_exists('to', $data) && ! array_key_exists('from', $data)) {
            throw new FilterException('Accepted operators for date filter not found. Accepted types: ' . implode(', ', $this->acceptedDateOperators), 400);
        }

        /**
         * Loop over the operators to search for entries based on a date
         *
         * Will only search from a specific date
         * @example filters[created_at][from]=2022-02-04
         *
         * Will only search to a specific date
         * @example filters[created_at][to]=2022-02-04
         *
         * Will search between two dates because the where query will stack
         * @example filters[created_at][from]=2022-02-04&filters[created_at][to]=2022-03-05
         */
        foreach ($data as $operator => $value) {

            if (! in_array($operator, $this->acceptedDateOperators)) {
                throw new FilterException('Unaccepted operator for date type. Accepted types: ' . implode(', ', $this->acceptedDateOperators), 400);
            }

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

            $this->builder->where($field, $this->operators[$operator], $value->toDateTimeString());
        }

        return $this->builder;
    }

    /**
     * Sort the carts by the given order and field.
     *
     * @example filters[sort][asc]=price
     *
     * @param array $data
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function sort(array $data = []) : Builder
    {
        foreach ($data as $operator => $attribute) {

            if (! in_array($operator, $this->acceptedSortOperators)) {
                throw new FilterException('Unaccepted operator for sort type. Accepted types: ' . implode(', ', $this->acceptedSortOperators), 400);
            }

            $this->builder->orderBy($attribute, $this->operators[$operator]);
        }
        return $this->builder;
    }
}
