<?php

declare(strict_types=1);

namespace ImovelZapAi\Analytics\Application\Actions;

use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Event;
use ImovelZapAi\Analytics\Domain\Enums\AnalyticsEventKey;
use ImovelZapAi\Analytics\Domain\Enums\MetricKey;
use ImovelZapAi\Analytics\Domain\Events\AnalyticsFactRecorded;
use ImovelZapAi\Analytics\Infrastructure\Persistence\Eloquent\AnalyticsDailyMetricModel;
use ImovelZapAi\Analytics\Infrastructure\Persistence\Eloquent\AnalyticsFactModel;

final class RecordAnalyticsFactAction
{
    public function execute(
        string $tenantId,
        AnalyticsEventKey $eventKey,
        ?Carbon $occurredAt = null,
        ?string $entityType = null,
        ?string $entityId = null,
        ?int $userId = null,
        ?string $amount = null,
        array $meta = [],
    ): AnalyticsFactModel {
        $occurredAt ??= now();

        $fact = AnalyticsFactModel::query()->create([
            'tenant_id' => $tenantId,
            'event_key' => $eventKey->value,
            'occurred_at' => $occurredAt,
            'entity_type' => $entityType,
            'entity_id' => $entityId,
            'user_id' => $userId,
            'amount' => $amount,
            'meta' => $meta === [] ? null : $meta,
            'created_at' => now(),
        ]);

        $this->bumpDailyMetrics($tenantId, $eventKey, $occurredAt, $userId, $amount, $meta);

        Event::dispatch(AnalyticsFactRecorded::fromModel($fact));

        return $fact;
    }

    /** @param array<string, mixed> $meta */
    private function bumpDailyMetrics(
        string $tenantId,
        AnalyticsEventKey $eventKey,
        Carbon $occurredAt,
        ?int $userId,
        ?string $amount,
        array $meta,
    ): void {
        $metricDate = $occurredAt->toDateString();
        $dimensions = [null];

        if ($userId !== null) {
            $dimensions[] = 'user:'.$userId;
        }

        $source = $meta['source'] ?? null;
        if (is_string($source) && $source !== '') {
            $dimensions[] = 'channel:'.$source;
        }

        foreach ($eventKey->metricKeys() as $metricKey) {
            foreach (array_unique($dimensions) as $dimensionKey) {
                $increment = $this->incrementFor($metricKey, $amount, $meta);
                if ($increment <= 0) {
                    continue;
                }

                $existing = AnalyticsDailyMetricModel::query()
                    ->where('tenant_id', $tenantId)
                    ->whereDate('metric_date', $metricDate)
                    ->where('metric_key', $metricKey->value)
                    ->when(
                        $dimensionKey === null,
                        fn ($q) => $q->whereNull('dimension_key'),
                        fn ($q) => $q->where('dimension_key', $dimensionKey),
                    )
                    ->first();

                if ($existing) {
                    $existing->update([
                        'value' => bcadd((string) $existing->value, (string) $increment, 2),
                    ]);
                } else {
                    AnalyticsDailyMetricModel::query()->create([
                        'tenant_id' => $tenantId,
                        'metric_date' => $metricDate,
                        'metric_key' => $metricKey->value,
                        'dimension_key' => $dimensionKey,
                        'value' => $increment,
                    ]);
                }
            }
        }
    }

    /** @param array<string, mixed> $meta */
    private function incrementFor(MetricKey $metricKey, ?string $amount, array $meta): float
    {
        if ($metricKey === MetricKey::VolumeNegotiated || $metricKey === MetricKey::PaymentsReceivedAmount) {
            $value = $amount ?? ($meta['amount'] ?? null);

            return $value !== null ? (float) $value : 0.0;
        }

        return 1.0;
    }
}
