<?php
declare(strict_types=1);

namespace FCA\StockApi\Collection\Filter;

use FCA\StockApi\Collection\Filter\Builder\Validator;
use FCA\StockApi\Exception\ApiException;

/**
 * Class Builder
 * @package FCA\StockApi\Collection\Filter
 */
class Builder
{
    public function orWhere(array ...$filters): array
    {
        foreach ($filters as $filter) {
            Validator::validateFilter($filter);
        }

        if (count($filters) > 1) {
            return [
                '$or' => $filters
            ];
        } elseif (count($filters) === 1) {
            return $filters[0];
        } else {
            return [];
        }
    }

    public function andWhere(array ...$filters): array
    {
        foreach ($filters as $filter) {
            Validator::validateFilter($filter);
        }

        if (count($filters) > 1) {
            return [
                '$and' => $filters
            ];
        } elseif (count($filters) === 1) {
            return $filters[0];
        } else {
            return [];
        }
    }

    public function not(array $filter): array
    {
        Validator::validateExpression($filter);

        $key = array_keys($filter)[0];
        return [
            $key => [
                '$not' => $filter[$key]
            ]
        ];
    }

    /**
     * @param string $field
     * @param string $operator (Valid values: =, !=, <, <=, >, >=, in, !in)
     * @param mixed $value
     *
     * @return array
     * @throws ApiException
     */
    public function is(string $field, string $operator, $value): array
    {
        // check if field is valid
        if (!Validator::isValidField($field)) {
            throw new ApiException('Invalid field "' . $field . '"');
        }

        // check if value type is valid
        if (in_array($operator, ['in', '!in', 'all'], true)) {
            if (!is_array($value)) {
                throw new ApiException('Value must be an array!');
            }

            foreach ($value as $val) {
                if (!Validator::isValidValue($field, $val)) {
                    $valid_types = Builder\ValueTypes::getValueMap()[$field] ?? false;
                    throw new ApiException(
                        'Invalid value type ("' . $field . '"): ' . gettype($val)
                        . ($valid_types ? ', whereas valid types are: ' . implode(', ', (array) $valid_types) : '')
                    );
                }
            }
        } else {
            if (!Validator::isValidValue($field, $value)) {
                $valid_types = Builder\ValueTypes::getValueMap()[$field] ?? false;
                throw new ApiException(
                    'Invalid value type ("' . $field . '")'
                    . ($valid_types ? ', whereas valid types are: ' . implode(', ', (array) $valid_types) : '')
                );
            }
        }

        // return array or throw exception when operator is not valid
        if ($operator === '<=') {
            return [$field => ['$lte' => $value]];
        } elseif ($operator === '<') {
            return [$field => ['$lt' => $value]];
        } elseif ($operator === '>=') {
            return [$field => ['$gte' => $value]];
        } elseif ($operator === '>') {
            return [$field => ['$gt' => $value]];
        } elseif ($operator === '=') {
            return [$field => $value];
        } elseif ($operator === '!=') {
            return [$field => ['$ne' => $value]];
        } elseif ($operator === 'in') {
            return [$field => ['$in' => $value]];
        } elseif ($operator === '!in') {
            return [$field => ['$nin' => $value]];
        } elseif ($operator === 'all') {
            return [$field => ['$all' => $value]];
        } else {
            throw new ApiException('Invalid operator! (Valid values: =, !=, <, <=, >, >=, in, !in, all)');
        }
    }

    /**
     * @param string $field
     * @param float  $longitude
     * @param float  $latitude
     * @param int    $maxDistance Max distance in kilometers
     *
     * @return array
     * @throws ApiException
     */
    public function isNear(string $field, float $longitude, float $latitude, int $maxDistance): array
    {
        $filter = [
            $field => [
                '$geoWithin' => [
                    '$centerSphere' => [
                        [$longitude, $latitude],
                        $maxDistance / 6378.1 // 6378.1 km is Earth radius, imagine that! This converts kilometers to radius
                    ],
                ]
            ]
        ];

        Validator::validateFilter($filter);

        return $filter;
    }
}
