<?php

declare(strict_types=1);

namespace Bastion\Handlers;

use Symfony\Component\HttpFoundation\JsonResponse;
use Openguru\OpenCartFramework\Http\Request;
use Openguru\OpenCartFramework\QueryBuilder\Builder;
use Openguru\OpenCartFramework\QueryBuilder\RawExpression;
use Openguru\OpenCartFramework\Support\Arr;

class TelegramCustomersHandler
{
    private const TABLE_NAME = 'telecart_customers';
    private const DEFAULT_PAGE = 1;
    private const DEFAULT_ROWS = 20;
    private const DEFAULT_SORT_FIELD = 'last_seen_at';
    private const DEFAULT_SORT_ORDER = 'DESC';

    private Builder $builder;

    public function __construct(Builder $builder)
    {
        $this->builder = $builder;
    }

    /**
     * Получить список Telegram-кастомеров с пагинацией, фильтрацией и сортировкой
     *
     * @param Request $request HTTP запрос с параметрами пагинации, сортировки и фильтров
     * @return JsonResponse JSON ответ с данными и метаинформацией
     */
    public function getCustomers(Request $request): JsonResponse
    {
        $page = max(1, (int) $request->json('page', self::DEFAULT_PAGE));
        $rows = max(1, (int) $request->json('rows', self::DEFAULT_ROWS));
        $first = ($page - 1) * $rows;

        $sortField = $request->json('sortField', self::DEFAULT_SORT_FIELD) ?? self::DEFAULT_SORT_FIELD;
        $sortOrder = $this->normalizeSortOrder((string)$request->json('sortOrder', self::DEFAULT_SORT_ORDER));

        $filters = $request->json('filters', []);
        $globalFilter = Arr::get($filters, 'global.value');

        // Создаем базовый query с фильтрами
        $query = $this->buildBaseQuery();
        $this->applyFilters($query, $filters, $globalFilter);

        // Получаем общее количество записей
        $countQuery = $this->buildCountQuery();
        $this->applyFilters($countQuery, $filters, $globalFilter);
        $totalRecords = (int) ($countQuery->value('total') ?? 0);

        // Применяем сортировку и пагинацию
        $customers = $query
            ->orderBy($sortField, $sortOrder)
            ->offset($first)
            ->limit($rows)
            ->get();

        return new JsonResponse([
            'data' => [
                'data' => $this->mapToResponse($customers),
                'totalRecords' => $totalRecords,
            ],
        ]);
    }

    /**
     * Создать базовый query для выборки данных
     *
     * @return Builder
     */
    private function buildBaseQuery(): Builder
    {
        return $this->builder->newQuery()
            ->select([
                'id',
                'telegram_user_id',
                'oc_customer_id',
                'tracking_id',
                'username',
                'first_name',
                'last_name',
                'language_code',
                'is_premium',
                'allows_write_to_pm',
                'photo_url',
                'last_seen_at',
                'referral',
                'orders_count',
                'privacy_consented_at',
                'created_at',
                'updated_at',
            ])
            ->from(self::TABLE_NAME);
    }

    /**
     * Создать query для подсчета общего количества записей
     *
     * @return Builder
     */
    private function buildCountQuery(): Builder
    {
        return $this->builder->newQuery()
            ->select([new RawExpression('COUNT(*) as total')])
            ->from(self::TABLE_NAME);
    }

    /**
     * Применить фильтры к query
     *
     * @param Builder $query Query builder
     * @param array $filters Массив фильтров
     * @param string|null $globalFilter Глобальный фильтр поиска
     * @return void
     */
    private function applyFilters(Builder $query, array $filters, ?string $globalFilter): void
    {
        // Применяем глобальный фильтр
        if ($globalFilter) {
            $this->applyGlobalFilter($query, $globalFilter);
        }

        // Применяем фильтры по колонкам
        $this->applyColumnFilters($query, $filters);
    }

    /**
     * Применить глобальный фильтр поиска
     *
     * @param Builder $query Query builder
     * @param string $searchTerm Поисковый запрос
     * @return void
     */
    private function applyGlobalFilter(Builder $query, string $searchTerm): void
    {
        $query->whereNested(function ($q) use ($searchTerm) {
            $q->where('telegram_user_id', 'LIKE', "%{$searchTerm}%")
                ->orWhere('username', 'LIKE', "%{$searchTerm}%")
                ->orWhere('first_name', 'LIKE', "%{$searchTerm}%")
                ->orWhere('last_name', 'LIKE', "%{$searchTerm}%")
                ->orWhere('language_code', 'LIKE', "%{$searchTerm}%");
        });
    }

    /**
     * Применить фильтры по колонкам
     *
     * @param Builder $query Query builder
     * @param array $filters Массив фильтров
     * @return void
     */
    private function applyColumnFilters(Builder $query, array $filters): void
    {
        foreach ($filters as $field => $filter) {
            if ($field === 'global') {
                continue;
            }

            // Обработка сложных фильтров (constraints)
            if (isset($filter['constraints']) && is_array($filter['constraints'])) {
                $this->applyConstraintFilters($query, $field, $filter);
                continue;
            }

            // Обработка простых фильтров (обратная совместимость)
            if (! isset($filter['value']) || $filter['value'] === null || $filter['value'] === '') {
                continue;
            }

            $value = $filter['value'];
            $matchMode = Arr::get($filter, 'matchMode', 'contains');

            $this->applyColumnFilter($query, $field, $value, $matchMode);
        }
    }

    /**
     * Применить сложные фильтры с условиями (AND/OR)
     *
     * @param Builder $query Query builder
     * @param string $field Имя поля
     * @param array $filter Данные фильтра
     * @return void
     */
    private function applyConstraintFilters(Builder $query, string $field, array $filter): void
    {
        $operator = strtolower($filter['operator'] ?? 'and');
        $constraints = $filter['constraints'];

        // Фильтруем пустые значения (но учитываем false как валидное значение для boolean полей)
        $activeConstraints = array_filter($constraints, function ($constraint) {
            if (!isset($constraint['value'])) {
                return false;
            }
            $value = $constraint['value'];
            // null означает "любой", пропускаем
            if ($value === null) {
                return false;
            }
            // Пустая строка пропускаем
            if ($value === '') {
                return false;
            }
            // false - валидное значение для boolean полей
            return true;
        });

        if (empty($activeConstraints)) {
            return;
        }

        $query->whereNested(function ($q) use ($field, $activeConstraints, $operator) {
            // Для первого элемента всегда используем where, чтобы начать группу
            $first = true;

            foreach ($activeConstraints as $constraint) {
                $value = $constraint['value'];
                $matchMode = $constraint['matchMode'] ?? 'contains';

                if ($first) {
                    $this->applyColumnFilter($q, $field, $value, $matchMode);
                    $first = false;
                    continue;
                }

                if ($operator === 'or') {
                    $q->orWhere(function ($subQ) use ($field, $value, $matchMode) {
                        $this->applyColumnFilter($subQ, $field, $value, $matchMode);
                    });
                } else {
                    $this->applyColumnFilter($q, $field, $value, $matchMode);
                }
            }
        });
    }

    /**
     * Применить фильтр для одной колонки
     *
     * @param Builder $query Query builder
     * @param string $field Имя поля
     * @param mixed $value Значение фильтра
     * @param string $matchMode Режим совпадения (contains, startsWith, endsWith, equals, notEquals)
     * @return void
     */
    private function applyColumnFilter(Builder $query, string $field, $value, string $matchMode): void
    {
        if (in_array($matchMode, ['contains', 'startsWith', 'endsWith'], true)) {
            $likeValue = $this->buildLikeValue($value, $matchMode);
            $query->where($field, 'LIKE', $likeValue);
        } elseif ($matchMode === 'equals') {
            $query->where($field, '=', $value);
        } elseif ($matchMode === 'notEquals') {
            $query->where($field, '!=', $value);
        } elseif ($matchMode === 'gt') {
            $query->where($field, '>', $value);
        } elseif ($matchMode === 'lt') {
            $query->where($field, '<', $value);
        } elseif ($matchMode === 'gte') {
            $query->where($field, '>=', $value);
        } elseif ($matchMode === 'lte') {
            $query->where($field, '<=', $value);
        } elseif ($matchMode === 'dateIs') {
            // Для точного совпадения даты используем диапазон от 00:00:00 до 23:59:59
            $date = date('Y-m-d', strtotime($value));
            $query->where($field, '>=', $date . ' 00:00:00')
                  ->where($field, '<=', $date . ' 23:59:59');
        } elseif ($matchMode === 'dateIsNot') {
            // Для отрицания проверяем, что дата меньше начала дня ИЛИ больше конца дня
            $date = date('Y-m-d', strtotime($value));
            $query->whereNested(function ($q) use ($field, $date) {
                $q->where($field, '<', $date . ' 00:00:00')
                  ->orWhere($field, '>', $date . ' 23:59:59');
            });
        } elseif ($matchMode === 'dateBefore') {
            $query->where($field, '<', date('Y-m-d 00:00:00', strtotime($value)));
        } elseif ($matchMode === 'dateAfter') {
            // "После" означает после конца указанного дня
            $query->where($field, '>', date('Y-m-d 23:59:59', strtotime($value)));
        }
    }

    /**
     * Построить значение для LIKE запроса
     *
     * @param string $value Значение
     * @param string $matchMode Режим совпадения
     * @return string
     */
    private function buildLikeValue(string $value, string $matchMode): string
    {
        if ($matchMode === 'startsWith') {
            return "{$value}%";
        }

        if ($matchMode === 'endsWith') {
            return "%{$value}";
        }

        return "%{$value}%";
    }

    /**
     * Нормализовать порядок сортировки
     *
     * @param string $sortOrder Порядок сортировки
     * @return string 'ASC' или 'DESC'
     */
    private function normalizeSortOrder(string $sortOrder): string
    {
        $normalized = strtoupper($sortOrder);

        return in_array($normalized, ['ASC', 'DESC'], true) ? $normalized : self::DEFAULT_SORT_ORDER;
    }

    private function mapToResponse(array $customers): array
    {
        return array_map(static function (array $customer) {
            return [
                'id' => (int) $customer['id'],
                'telegram_user_id' => (int) $customer['telegram_user_id'],
                'oc_customer_id' => (int) $customer['oc_customer_id'],
                'tracking_id' => $customer['tracking_id'],
                'username' => $customer['username'],
                'first_name' => $customer['first_name'],
                'last_name' => $customer['last_name'],
                'language_code' => $customer['language_code'],
                'is_premium' => filter_var($customer['is_premium'], FILTER_VALIDATE_BOOLEAN),
                'allows_write_to_pm' => filter_var($customer['allows_write_to_pm'], FILTER_VALIDATE_BOOLEAN),
                'photo_url' => $customer['photo_url'],
                'last_seen_at' => $customer['last_seen_at'],
                'referral' => $customer['referral'],
                'orders_count' => (int) $customer['orders_count'],
                'privacy_consented_at' => $customer['privacy_consented_at'],
                'created_at' => $customer['created_at'],
                'updated_at' => $customer['updated_at'],
            ];
        }, $customers);
    }
}
