<?php

declare(strict_types=1);

namespace App\Services\Profile;

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

/**
 * Detects inactivity gaps in a creator's public artwork history and
 * returns milestone rows for any comeback events.
 *
 * Thresholds:
 *   Minor:     180–364 days gap
 *   Major:     365–1094 days gap (1–3 years)
 *   Legendary: 1095+ days gap (3+ years)
 */
final class CreatorComebackService
{
    private const MINOR_DAYS     = 180;
    private const MAJOR_DAYS     = 365;
    private const LEGENDARY_DAYS = 1095;

    /**
     * Given the ordered collection of public artwork rows (ascending by published_at),
     * detect all comeback events and return milestone row arrays.
     *
     * @param  Collection<int, object>  $artworks   rows from publicArtworkRows()
     * @param  int                      $userId
     * @param  CarbonInterface          $computedAt
     * @param  callable(int, CreatorMilestoneType, CarbonInterface, array, ?int, CarbonInterface): array  $makeMilestoneRow
     * @return array<int, array<string, mixed>>
     */
    public function calculateComebacks(
        Collection $artworks,
        int $userId,
        CarbonInterface $computedAt,
        callable $makeMilestoneRow,
    ): array {
        if ($artworks->count() < 2) {
            return [];
        }

        $sorted = $artworks
            ->filter(fn (object $row): bool => ! empty($row->published_at))
            ->sortBy([['published_at', 'asc'], ['id', 'asc']])
            ->values();

        $milestones = [];
        $prevDate = null;

        foreach ($sorted as $artwork) {
            $currentDate = $this->parseDate($artwork->published_at);

            if ($prevDate !== null && $currentDate !== null) {
                $gapDays = (int) $prevDate->diffInDays($currentDate);

                $type = $this->comebackTypeForGap($gapDays);

                if ($type !== null) {
                    $milestones[] = $makeMilestoneRow(
                        $userId,
                        $type,
                        $currentDate,
                        $this->buildPayload($type, $gapDays, $prevDate, $artwork),
                        (int) $artwork->id,
                        $computedAt,
                    );

                    // Only record one comeback per gap: if we match legendary, skip major/minor for same gap.
                    // prevDate resets after each comeback so consecutive short-gap uploads won't double-count.
                }
            }

            // Only advance prevDate when the gap did NOT trigger a comeback.
            // After a comeback, the "chain" resets from the new return date.
            $prevDate = $currentDate;
        }

        return $milestones;
    }

    private function comebackTypeForGap(int $gapDays): ?CreatorMilestoneType
    {
        if ($gapDays >= self::LEGENDARY_DAYS) {
            return CreatorMilestoneType::ComebackLegendary;
        }

        if ($gapDays >= self::MAJOR_DAYS) {
            return CreatorMilestoneType::ComebackMajor;
        }

        if ($gapDays >= self::MINOR_DAYS) {
            return CreatorMilestoneType::ComebackMinor;
        }

        return null;
    }

    /**
     * @return array<string, mixed>
     */
    private function buildPayload(
        CreatorMilestoneType $type,
        int $gapDays,
        CarbonInterface $previousUploadAt,
        object $artwork,
    ): array {
        $years = (int) round($gapDays / 365);
        $months = (int) round($gapDays / 30);

        $durationLabel = match (true) {
            $years >= 3   => $years . ' years',
            $years >= 1   => $years === 1 ? 'a year' : $years . ' years',
            $months >= 2  => $months . ' months',
            default       => 'several months',
        };

        $summaryMap = [
            CreatorMilestoneType::ComebackMinor->value     => "Returned to Skinbase after {$durationLabel} away with a new public upload.",
            CreatorMilestoneType::ComebackMajor->value     => "Major comeback after {$durationLabel} away — new work published again on Skinbase.",
            CreatorMilestoneType::ComebackLegendary->value => "Returned to Skinbase after {$durationLabel} away, picking up where the journey left off.",
        ];

        $titleMap = [
            CreatorMilestoneType::ComebackMinor->value     => 'Comeback',
            CreatorMilestoneType::ComebackMajor->value     => 'Major comeback',
            CreatorMilestoneType::ComebackLegendary->value => 'Legendary comeback',
        ];

        return [
            'title'   => $titleMap[$type->value] ?? 'Comeback',
            'headline' => (string) $artwork->title,
            'summary'  => $summaryMap[$type->value] ?? "Returned after {$durationLabel}.",
            'value'    => "After {$durationLabel}",
            'artwork'  => $this->artworkSnapshot($artwork),
            'metadata' => [
                'previous_upload_at' => $previousUploadAt->toIso8601String(),
                'gap_days'           => $gapDays,
                'comeback_level'     => $this->levelLabel($type),
            ],
        ];
    }

    private function levelLabel(CreatorMilestoneType $type): string
    {
        return match ($type) {
            CreatorMilestoneType::ComebackMinor     => 'minor',
            CreatorMilestoneType::ComebackMajor     => 'major',
            CreatorMilestoneType::ComebackLegendary => 'legendary',
            default                                 => 'minor',
        };
    }

    /**
     * @return array<string, mixed>
     */
    private function artworkSnapshot(object $artwork): array
    {
        return [
            'id'           => (int) $artwork->id,
            'title'        => (string) $artwork->title,
            'slug'         => (string) ($artwork->slug ?? $artwork->id),
            'published_at' => $this->parseDate($artwork->published_at)?->toIso8601String(),
        ];
    }

    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;
        }
    }
}
