<?php

namespace App\Handlers;

use Carbon\Carbon;
use Openguru\OpenCartFramework\Config\Settings;
use Openguru\OpenCartFramework\Exceptions\InvalidApiTokenException;
use Symfony\Component\HttpFoundation\JsonResponse;
use Openguru\OpenCartFramework\Http\Request;
use Openguru\OpenCartFramework\QueryBuilder\Builder;
use Openguru\OpenCartFramework\QueryBuilder\RawExpression;
use Openguru\OpenCartFramework\Support\DateUtils;
use Psr\Log\LoggerInterface;

class ETLHandler
{
    private Builder $builder;
    private Settings $settings;
    private LoggerInterface $logger;

    public function __construct(Builder $builder, Settings $settings, LoggerInterface $logger)
    {
        $this->builder = $builder;
        $this->settings = $settings;
        $this->logger = $logger;
    }

    private function getLastUpdatedAtSql(): string
    {
        return '
            GREATEST(
                COALESCE((
                    SELECT MAX(date_modified)
                    FROM oc_order as o
                    where o.customer_id = telecart_customers.oc_customer_id
                ), 0),
                telecart_customers.updated_at
            )
        ';
    }

    private function getCustomerQuery(?Carbon $updatedAt = null): Builder
    {
        $lastUpdatedAtSql = $this->getLastUpdatedAtSql();

        return $this->builder->newQuery()
            ->from('telecart_customers')
            ->where('allows_write_to_pm', '=', 1)
            ->when($updatedAt !== null, function (Builder $builder) use ($lastUpdatedAtSql, $updatedAt) {
                $builder->where(new RawExpression($lastUpdatedAtSql), '>=', $updatedAt);
            });
    }

    /**
     * @throws InvalidApiTokenException
     */
    public function getCustomersMeta(Request $request): JsonResponse
    {
        $this->validateApiKey($request);

        $updatedAt = $request->get('updated_at');
        if ($updatedAt) {
            $updatedAt = DateUtils::toSystemTimezone($updatedAt);
        }
        $query = $this->getCustomerQuery($updatedAt);
        $total = $query->count();

        return new JsonResponse([
            'data' => [
                'total' => $total,
            ],
        ]);
    }

    /**
     * @throws InvalidApiTokenException
     */
    public function customers(Request $request): JsonResponse
    {
        $this->validateApiKey($request);

        $this->logger->debug('Get customers for ETL');

        $page = (int)$request->get('page', 1);
        $perPage = (int)$request->get('perPage', 10000);
        $successOrderStatusIds = '5,3';
        $updatedAt = $request->get('updated_at');
        if ($updatedAt) {
            $updatedAt = DateUtils::toSystemTimezone($updatedAt);
        }

        $lastUpdatedAtSql = $this->getLastUpdatedAtSql();
        $query = $this->getCustomerQuery($updatedAt);

        $query->orderBy('telegram_user_id');
        $query->forPage($page, $perPage);

        $query
            ->select([
                'tracking_id',
                'username',
                'photo_url',
                'telegram_user_id' => 'tg_user_id',
                'telecart_customers.oc_customer_id',
                'is_premium',
                'last_seen_at',
                'orders_count' => 'orders_count_total',
                'created_at' => 'registered_at',
                new RawExpression(
                    '(
                        SELECT MIN(date_added) 
                        FROM oc_order 
                        WHERE oc_order.customer_id = telecart_customers.oc_customer_id
                    ) AS first_order_date'
                ),
                new RawExpression(
                    '(
                        SELECT MAX(date_added) 
                        FROM oc_order 
                        WHERE oc_order.customer_id = telecart_customers.oc_customer_id
                    ) AS last_order_date'
                ),
                new RawExpression(
                    "COALESCE((
                    SELECT
                        SUM(total)
                    FROM
                        oc_order
                    WHERE
                        oc_order.customer_id = telecart_customers.oc_customer_id
                        AND oc_order.order_status_id IN ($successOrderStatusIds)
                ), 0) AS total_spent"
                ),
                new RawExpression(
                    "COALESCE((
                    SELECT
                        COUNT(*)
                    FROM
                        oc_order
                    WHERE
                        oc_order.customer_id = telecart_customers.oc_customer_id
                        AND oc_order.order_status_id IN ($successOrderStatusIds)
                ), 0) AS orders_count_success"
                ),
                new RawExpression("$lastUpdatedAtSql AS updated_at"),
            ]);

        $items = $query->get();

        return new JsonResponse([
            'data' => array_map(static function ($item) {
                return [
                    'tracking_id' => $item['tracking_id'],
                    'username' => $item['username'],
                    'photo_url' => $item['photo_url'],
                    'tg_user_id' => filter_var($item['tg_user_id'], FILTER_VALIDATE_INT),
                    'oc_customer_id' => filter_var($item['oc_customer_id'], FILTER_VALIDATE_INT),
                    'is_premium' => filter_var($item['is_premium'], FILTER_VALIDATE_BOOLEAN),
                    'last_seen_at' => DateUtils::toUTC($item['last_seen_at']),
                    'orders_count_total' => filter_var($item['orders_count_total'], FILTER_VALIDATE_INT),
                    'registered_at' => DateUtils::toUTC($item['registered_at']),
                    'first_order_date' => DateUtils::toUTC($item['first_order_date']),
                    'last_order_date' => DateUtils::toUTC($item['last_order_date']),
                    'total_spent' => (float)$item['total_spent'],
                    'orders_count_success' => filter_var($item['orders_count_success'], FILTER_VALIDATE_INT),
                    'updated_at' => DateUtils::toUTC($item['updated_at']),
                ];
            }, $items),
        ]);
    }

    /**
     * @throws InvalidApiTokenException
     */
    private function validateApiKey(Request $request): void
    {
        $token = $request->getApiKey();

        if (empty($token)) {
            throw new InvalidApiTokenException('Invalid API Key.');
        }

        if (strcmp($token, $this->settings->get('pulse.api_key')) !== 0) {
            throw new InvalidApiTokenException('Invalid API Key');
        }
    }
}
