<?php

declare(strict_types=1);

namespace App\Services\Profile;

use App\Enums\CreatorMilestoneType;
use Carbon\Carbon;
use Carbon\CarbonInterface;
use Illuminate\Support\Collection;

/**
 * Calculates upload streaks (consecutive calendar months with at least one public upload)
 * and active-year streaks (consecutive years with at least one public upload).
 *
 * Returns:
 *   - milestone rows for notable streak achievements
 *   - a streaks summary array for the API payload
 */
final class CreatorStreakService
{
    /**
     * Compute streak milestones from a creator's public artwork collection.
     *
     * @param  Collection<int, object>  $artworks
     * @param  int                      $userId
     * @param  CarbonInterface          $computedAt
     * @param  callable                 $makeMilestoneRow
     * @return array<int, array<string, mixed>>
     */
    public function calculateStreakMilestones(
        Collection $artworks,
        int $userId,
        CarbonInterface $computedAt,
        callable $makeMilestoneRow,
    ): array {
        if ($artworks->isEmpty()) {
            return [];
        }

        $milestones = [];
        $stats = $this->computeStreakStats($artworks);

        // Monthly upload streak milestones
        foreach ([12, 6, 3] as $months) {
            if ($stats['best_monthly_streak'] >= $months) {
                $type = match ($months) {
                    12 => CreatorMilestoneType::UploadStreak12,
                    6  => CreatorMilestoneType::UploadStreak6,
                    3  => CreatorMilestoneType::UploadStreak3,
                };

                $occurredAt = $stats['best_monthly_streak_end'] ?? $computedAt;

                $milestones[] = $makeMilestoneRow(
                    $userId,
                    $type,
                    $occurredAt,
                    [
                        'title'    => $months . '-month upload streak',
                        'headline' => "Published in {$months} consecutive months.",
                        'summary'  => "Maintained a public upload in every calendar month for {$months} consecutive months.",
                        'value'    => "{$months} months",
                        'metrics'  => [
                            'months'               => $months,
                            'best_monthly_streak'  => $stats['best_monthly_streak'],
                            'current_monthly_streak' => $stats['current_monthly_streak'],
                        ],
                    ],
                    null,
                    $computedAt,
                );

                break; // Only insert the best monthly streak milestone (e.g. if best=12, skip 6 and 3)
            }
        }

        // Active-year streak milestones
        foreach ([5, 3] as $years) {
            if ($stats['best_year_streak'] >= $years) {
                $type = match ($years) {
                    5 => CreatorMilestoneType::ActiveYearStreak5,
                    3 => CreatorMilestoneType::ActiveYearStreak3,
                };

                $occurredAt = $stats['best_year_streak_end'] ?? $computedAt;

                $milestones[] = $makeMilestoneRow(
                    $userId,
                    $type,
                    $occurredAt,
                    [
                        'title'    => "{$years}-year active streak",
                        'headline' => "Stayed active for {$years} consecutive years.",
                        'summary'  => "Published at least one public artwork every year for {$years} consecutive years.",
                        'value'    => "{$years} years",
                        'metrics'  => [
                            'years'             => $years,
                            'best_year_streak'  => $stats['best_year_streak'],
                            'current_year_streak' => $stats['current_year_streak'],
                        ],
                    ],
                    null,
                    $computedAt,
                );

                break; // Only insert the best year streak milestone
            }
        }

        return $milestones;
    }

    /**
     * Compute raw streak statistics for use in the API streaks payload.
     *
     * @param  Collection<int, object>  $artworks
     * @return array{
     *   current_monthly_streak: int,
     *   best_monthly_streak: int,
     *   best_monthly_streak_end: ?CarbonInterface,
     *   current_year_streak: int,
     *   best_year_streak: int,
     *   best_year_streak_end: ?CarbonInterface,
     * }
     */
    public function computeStreakStats(Collection $artworks): array
    {
        if ($artworks->isEmpty()) {
            return $this->emptyStats();
        }

        // Build sets of active months (YYYY-MM) and active years
        $activeMonths = [];
        $activeYears  = [];

        foreach ($artworks as $artwork) {
            $date = $this->parseDate($artwork->published_at);

            if ($date === null) {
                continue;
            }

            $activeMonths[$date->format('Y-m')] = $date;
            $activeYears[(int) $date->format('Y')] = $date;
        }

        if ($activeMonths === []) {
            return $this->emptyStats();
        }

        ksort($activeMonths);
        ksort($activeYears);

        return [
            ...$this->computeMonthlyStreaks($activeMonths),
            ...$this->computeYearlyStreaks($activeYears),
        ];
    }

    /**
     * @param  array<string, CarbonInterface>  $activeMonths  sorted ascending by key (YYYY-MM)
     * @return array{current_monthly_streak: int, best_monthly_streak: int, best_monthly_streak_end: ?CarbonInterface}
     */
    private function computeMonthlyStreaks(array $activeMonths): array
    {
        $now           = Carbon::now();
        $currentMonth  = $now->format('Y-m');

        $streak     = 1;
        $best       = 1;
        $bestEndDate = null;
        $prevKey    = null;
        $lastKey    = null;

        foreach ($activeMonths as $key => $date) {
            if ($prevKey !== null) {
                $expected = Carbon::parse($prevKey . '-01')->addMonth()->format('Y-m');

                if ($key === $expected) {
                    $streak++;
                } else {
                    $streak = 1;
                }
            }

            if ($streak > $best) {
                $best        = $streak;
                $bestEndDate = $date;
            }

            $prevKey = $key;
            $lastKey = $key;
        }

        // Current streak: walk backwards from current/last month
        $currentStreak = 0;
        $checkMonth    = $lastKey !== null ? Carbon::parse($lastKey . '-01') : $now->startOfMonth();

        // If the last active month is current or previous month, count the streak
        $diff = $now->startOfMonth()->diffInMonths($checkMonth);

        if ($diff <= 1) {
            $currentStreak = 1;
            $checkBack = $checkMonth->copy()->subMonth();

            while (isset($activeMonths[$checkBack->format('Y-m')])) {
                $currentStreak++;
                $checkBack->subMonth();
            }
        }

        return [
            'current_monthly_streak' => $currentStreak,
            'best_monthly_streak'    => $best,
            'best_monthly_streak_end' => $bestEndDate,
        ];
    }

    /**
     * @param  array<int, CarbonInterface>  $activeYears  sorted ascending by key (int year)
     * @return array{current_year_streak: int, best_year_streak: int, best_year_streak_end: ?CarbonInterface}
     */
    private function computeYearlyStreaks(array $activeYears): array
    {
        $currentYear = (int) Carbon::now()->year;

        $streak     = 1;
        $best       = 1;
        $bestEndDate = null;
        $prevYear   = null;
        $lastYear   = null;

        foreach ($activeYears as $year => $date) {
            if ($prevYear !== null) {
                if ($year === $prevYear + 1) {
                    $streak++;
                } else {
                    $streak = 1;
                }
            }

            if ($streak > $best) {
                $best        = $streak;
                $bestEndDate = $date;
            }

            $prevYear = $year;
            $lastYear = $year;
        }

        // Current year streak
        $currentStreak = 0;

        if ($lastYear !== null && ($lastYear === $currentYear || $lastYear === $currentYear - 1)) {
            $currentStreak = 1;
            $checkYear     = $lastYear - 1;

            while (isset($activeYears[$checkYear])) {
                $currentStreak++;
                $checkYear--;
            }
        }

        return [
            'current_year_streak'  => $currentStreak,
            'best_year_streak'     => $best,
            'best_year_streak_end' => $bestEndDate,
        ];
    }

    /**
     * @return array{current_monthly_streak: int, best_monthly_streak: int, best_monthly_streak_end: null, current_year_streak: int, best_year_streak: int, best_year_streak_end: null}
     */
    private function emptyStats(): array
    {
        return [
            'current_monthly_streak'  => 0,
            'best_monthly_streak'     => 0,
            'best_monthly_streak_end' => null,
            'current_year_streak'     => 0,
            'best_year_streak'        => 0,
            'best_year_streak_end'    => null,
        ];
    }

    private function parseDate(mixed $value): ?CarbonInterface
    {
        if ($value instanceof CarbonInterface) {
            return $value;
        }

        if (! is_string($value) || trim($value) === '') {
            return null;
        }

        try {
            return Carbon::parse($value);
        } catch (\Throwable) {
            return null;
        }
    }
}
