<?php

declare(strict_types=1);

namespace App\Console\Commands;

use App\Models\AcademyContentMetricDaily;
use App\Models\AcademyEvent;
use App\Models\AcademyLike;
use App\Models\AcademySave;
use App\Models\AcademySearchLog;
use App\Services\Academy\AcademyPopularityService;
use App\Support\AcademyAnalytics\AcademyAnalyticsContentType;
use Carbon\CarbonPeriod;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;

final class AcademyAnalyticsRollupCommand extends Command
{
    protected $signature = 'academy:analytics-rollup {--date=} {--from=} {--to=}';

    protected $description = 'Aggregate Academy analytics raw events and interaction records into daily content metrics';

    public function __construct(private readonly AcademyPopularityService $popularity)
    {
        parent::__construct();
    }

    public function handle(): int
    {
        [$from, $to] = $this->resolveRange();

        foreach (CarbonPeriod::create($from, $to) as $date) {
            $this->rollupDate(Carbon::parse($date));
            $this->line(sprintf('Rolled up Academy analytics for %s.', Carbon::parse($date)->toDateString()));
        }

        return self::SUCCESS;
    }

    private function rollupDate(Carbon $date): void
    {
        $start = $date->copy()->startOfDay();
        $end = $date->copy()->endOfDay();
        $metrics = [];
        $uniqueVisitors = [];
        $engagedDurations = [];

        AcademyEvent::query()
            ->whereBetween('occurred_at', [$start, $end])
            ->orderBy('id')
            ->chunkById(1000, function ($events) use (&$metrics, &$uniqueVisitors, &$engagedDurations): void {
                foreach ($events as $event) {
                    if ($event->is_bot || $event->is_admin || $event->is_suspicious) {
                        continue;
                    }

                    $key = $this->metricKey((string) ($event->content_type ?? ''), $event->content_id ? (int) $event->content_id : null);
                    $this->ensureMetric($metrics, (string) ($event->content_type ?? ''), $event->content_id ? (int) $event->content_id : null, $key);

                    $visitorKey = $event->user_id ? sprintf('user:%d', (int) $event->user_id) : trim((string) ($event->visitor_id ?? ''));
                    if ($visitorKey !== '') {
                        $uniqueVisitors[$key][$visitorKey] = true;
                    }

                    $eventType = (string) $event->event_type;
                    if (in_array($eventType, ['academy_page_view', 'academy_content_view', 'academy_lesson_view', 'academy_course_view', 'academy_prompt_pack_view', 'academy_challenge_view'], true)) {
                        $metrics[$key]['views']++;
                        if ($event->is_logged_in) {
                            $metrics[$key]['user_views']++;
                        } else {
                            $metrics[$key]['guest_views']++;
                        }
                        if ($event->is_subscriber) {
                            $metrics[$key]['subscriber_views']++;
                        }
                    }

                    if ($eventType === 'academy_engaged_view') {
                        $metrics[$key]['engaged_views']++;
                        $engagedDurations[$key][] = max(0, (int) ($event->metadata['engaged_seconds'] ?? 15));
                    }

                    if ($eventType === 'academy_scroll_50') {
                        $metrics[$key]['scroll_50']++;
                    }
                    if ($eventType === 'academy_scroll_75') {
                        $metrics[$key]['scroll_75']++;
                    }
                    if ($eventType === 'academy_scroll_100') {
                        $metrics[$key]['scroll_100']++;
                    }
                    if ($eventType === 'academy_prompt_copy') {
                        $metrics[$key]['prompt_copies']++;
                    }
                    if ($eventType === 'academy_prompt_negative_copy') {
                        $metrics[$key]['negative_prompt_copies']++;
                    }
                    if (in_array($eventType, ['academy_lesson_started', 'academy_course_started', 'academy_challenge_started'], true)) {
                        $metrics[$key]['starts']++;
                    }
                    if (in_array($eventType, ['academy_lesson_completed', 'academy_course_completed', 'academy_challenge_submitted'], true)) {
                        $metrics[$key]['completions']++;
                    }
                    if ($eventType === 'academy_upgrade_click') {
                        $metrics[$key]['upgrade_clicks']++;
                    }
                    if ($eventType === 'academy_premium_preview_view') {
                        $metrics[$key]['premium_preview_views']++;
                    }
                    if ($eventType === 'academy_search_result_click') {
                        $metrics[$key]['search_clicks']++;

                        $searchKey = $this->metricKey(AcademyAnalyticsContentType::SEARCH, null);
                        $this->ensureMetric($metrics, AcademyAnalyticsContentType::SEARCH, null, $searchKey);
                        $metrics[$searchKey]['search_clicks']++;
                    }
                }
            });

        foreach (AcademyLike::query()->whereBetween('created_at', [$start, $end])->get() as $like) {
            $key = $this->metricKey((string) $like->content_type, (int) $like->content_id);
            $this->ensureMetric($metrics, (string) $like->content_type, (int) $like->content_id, $key);
            $metrics[$key]['likes']++;
        }

        foreach (AcademySave::query()->whereBetween('created_at', [$start, $end])->get() as $save) {
            $key = $this->metricKey((string) $save->content_type, (int) $save->content_id);
            $this->ensureMetric($metrics, (string) $save->content_type, (int) $save->content_id, $key);
            $metrics[$key]['saves']++;
        }

        foreach (AcademySearchLog::query()->whereBetween('created_at', [$start, $end])->get() as $searchLog) {
            $key = $this->metricKey(AcademyAnalyticsContentType::SEARCH, null);
            $this->ensureMetric($metrics, AcademyAnalyticsContentType::SEARCH, null, $key);
            $metrics[$key]['search_impressions']++;
            if ((int) $searchLog->results_count === 0) {
                $metrics[$key]['bounce_count']++;
            }

            $visitorKey = $searchLog->user_id ? sprintf('user:%d', (int) $searchLog->user_id) : trim((string) ($searchLog->visitor_id ?? ''));
            if ($visitorKey !== '') {
                $uniqueVisitors[$key][$visitorKey] = true;
            }
        }

        foreach ($metrics as $key => $metric) {
            $metric['unique_visitors'] = isset($uniqueVisitors[$key]) ? count($uniqueVisitors[$key]) : 0;
            $metric['avg_engaged_seconds'] = isset($engagedDurations[$key]) && $engagedDurations[$key] !== []
                ? (int) round(array_sum($engagedDurations[$key]) / count($engagedDurations[$key]))
                : null;
            $metric['bounce_count'] = max((int) ($metric['bounce_count'] ?? 0), max(0, (int) $metric['views'] - (int) $metric['engaged_views']));
            $metric['popularity_score'] = $this->popularity->calculatePopularityScore($metric);
            $metric['conversion_score'] = $this->popularity->calculateConversionScore($metric);

            AcademyContentMetricDaily::query()->upsert([
                array_merge($metric, [
                    'date' => $date->copy()->startOfDay(),
                    'created_at' => now(),
                    'updated_at' => now(),
                ]),
            ], ['date', 'content_type', 'content_id'], [
                'views',
                'unique_visitors',
                'guest_views',
                'user_views',
                'subscriber_views',
                'engaged_views',
                'scroll_50',
                'scroll_75',
                'scroll_100',
                'likes',
                'saves',
                'prompt_copies',
                'negative_prompt_copies',
                'starts',
                'completions',
                'upgrade_clicks',
                'premium_preview_views',
                'search_impressions',
                'search_clicks',
                'bounce_count',
                'avg_engaged_seconds',
                'popularity_score',
                'conversion_score',
                'updated_at',
            ]);
        }
    }

    /**
     * @param  array<string, array<string, mixed>>  $metrics
     */
    private function ensureMetric(array &$metrics, string $contentType, ?int $contentId, string $key): void
    {
        if (isset($metrics[$key])) {
            return;
        }

        $metrics[$key] = [
            'content_type' => $contentType,
            'content_id' => $contentId,
            'views' => 0,
            'unique_visitors' => 0,
            'guest_views' => 0,
            'user_views' => 0,
            'subscriber_views' => 0,
            'engaged_views' => 0,
            'scroll_50' => 0,
            'scroll_75' => 0,
            'scroll_100' => 0,
            'likes' => 0,
            'saves' => 0,
            'prompt_copies' => 0,
            'negative_prompt_copies' => 0,
            'starts' => 0,
            'completions' => 0,
            'upgrade_clicks' => 0,
            'premium_preview_views' => 0,
            'search_impressions' => 0,
            'search_clicks' => 0,
            'bounce_count' => 0,
            'avg_engaged_seconds' => null,
            'popularity_score' => 0,
            'conversion_score' => 0,
        ];
    }

    private function metricKey(string $contentType, ?int $contentId): string
    {
        return sprintf('%s:%s', $contentType, $contentId ?? 'none');
    }

    /**
     * @return array{0: Carbon, 1: Carbon}
     */
    private function resolveRange(): array
    {
        $date = $this->option('date');
        $from = $this->option('from');
        $to = $this->option('to');

        if (is_string($date) && trim($date) !== '') {
            $resolved = Carbon::parse($date)->startOfDay();

            return [$resolved, $resolved->copy()];
        }

        $resolvedFrom = is_string($from) && trim($from) !== ''
            ? Carbon::parse($from)->startOfDay()
            : now()->subDay()->startOfDay();
        $resolvedTo = is_string($to) && trim($to) !== ''
            ? Carbon::parse($to)->startOfDay()
            : $resolvedFrom->copy();

        return [$resolvedFrom, $resolvedTo];
    }
}