<?php

declare(strict_types=1);

namespace App\Services\Profile;

use App\Enums\CreatorMilestoneType;
use App\Models\Artwork;
use App\Models\CreatorEra;
use App\Models\User;
use Carbon\Carbon;
use Carbon\CarbonInterface;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

/**
 * Generates deterministic creator eras from a creator's public artwork history.
 *
 * Era types (assigned in order):
 *   early_years   – from first upload until a breakthrough signal
 *   breakthrough  – starts at first featured artwork or first major download milestone
 *   experimental  – detected when a creator shows high category/tag diversity with lower volume
 *   comeback      – starts after a significant inactivity gap (180+ days) followed by new publishing
 *   current       – the latest ongoing active phase (always set for active creators)
 *
 * Rules:
 *   - Only public artworks are considered.
 *   - Era boundaries are determined by key events (features, comebacks).
 *   - At most one era of each non-current type is created per rebuild.
 *   - The "current" era is always the last active phase.
 */
final class CreatorEraService
{
    private const COMEBACK_GAP_DAYS = 180;

    /**
     * Rebuild all eras for a user: delete existing rows and reinsert computed ones.
     *
     * @param  Collection<int, object>  $artworks  public artwork rows (ascending by published_at)
     */
    public function rebuildForUser(User $user, Collection $artworks): void
    {
        $eras = $this->computeEras($user, $artworks);

        DB::transaction(function () use ($user, $eras): void {
            CreatorEra::query()->where('user_id', (int) $user->id)->delete();

            if ($eras !== []) {
                DB::table('creator_eras')->insert($eras);
            }
        });
    }

    /**
     * Return the public era payload for the journey API.
     *
     * @return list<array<string, mixed>>
     */
    public function publicErasForUser(int $userId): array
    {
        return CreatorEra::query()
            ->where('user_id', $userId)
            ->orderBy('starts_at')
            ->get()
            ->map(fn (CreatorEra $era): array => $this->formatEra($era))
            ->values()
            ->all();
    }

    /**
     * Compute milestone rows for era_started events.
     *
     * @param  Collection<int, object>  $artworks
     * @return array<int, array<string, mixed>>
     */
    public function calculateEraMilestones(
        User $user,
        Collection $artworks,
        CarbonInterface $computedAt,
        callable $makeMilestoneRow,
    ): array {
        if ($artworks->isEmpty()) {
            return [];
        }

        $eras = $this->computeEras($user, $artworks);
        $milestones = [];

        foreach ($eras as $era) {
            if (in_array($era['era_type'], ['early_years', 'current'], true)) {
                continue; // Only notable era transitions get milestone rows
            }

            $occurredAt = Carbon::parse($era['starts_at']);

            $milestones[] = $makeMilestoneRow(
                (int) $user->id,
                CreatorMilestoneType::EraStarted,
                $occurredAt,
                [
                    'title'    => 'New era',
                    'headline' => $era['title'],
                    'summary'  => $era['description'] ?? 'A new creative phase began.',
                    'value'    => $era['title'],
                    'metadata' => ['era_type' => $era['era_type']],
                ],
                null,
                $computedAt,
            );
        }

        return $milestones;
    }

    /**
     * @param  Collection<int, object>  $artworks
     * @return array<int, array<string, mixed>>
     */
    private function computeEras(User $user, Collection $artworks): array
    {
        if ($artworks->isEmpty()) {
            return [];
        }

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

        if ($sorted->isEmpty()) {
            return [];
        }

        $now       = Carbon::now();
        $userId    = (int) $user->id;
        $eras      = [];

        $firstArtwork   = $sorted->first();
        $firstDate      = Carbon::parse($firstArtwork->published_at);
        $lastArtwork    = $sorted->last();
        $lastDate       = Carbon::parse($lastArtwork->published_at);

        // Detect featured date (breakthrough signal)
        $firstFeaturedAt  = $this->firstFeaturedDate($userId);
        $firstMajorDownloadAt = $this->firstMajorDownloadDate($sorted);

        // Detect comeback gap
        $comebackDate = $this->firstComebackDate($sorted);

        // Phase boundaries
        $breakthroughAt = match (true) {
            $firstFeaturedAt !== null  => $firstFeaturedAt,
            $firstMajorDownloadAt !== null => $firstMajorDownloadAt,
            default                        => null,
        };

        // ── Early Years ────────────────────────────────────────────────────
        $earlyYearsEnds = $breakthroughAt?->copy()->subSecond()
            ?? $comebackDate?->copy()->subSecond()
            ?? null;

        $eras[] = [
            'user_id'     => $userId,
            'era_type'    => 'early_years',
            'title'       => 'Early Years',
            'description' => 'The beginning of the creative journey on Skinbase.',
            'starts_at'   => $firstDate->toDateTimeString(),
            'ends_at'     => $earlyYearsEnds?->toDateTimeString(),
            'is_current'  => false,
            'metadata'    => json_encode($this->eraMetadata($sorted, $firstDate, $earlyYearsEnds ?? $lastDate)),
            'created_at'  => $now->toDateTimeString(),
            'updated_at'  => $now->toDateTimeString(),
        ];

        // ── Breakthrough Era ───────────────────────────────────────────────
        if ($breakthroughAt !== null) {
            $breakthroughEnds = $comebackDate?->copy()->subSecond() ?? null;

            $eras[] = [
                'user_id'     => $userId,
                'era_type'    => 'breakthrough',
                'title'       => 'Breakthrough Era',
                'description' => 'A period marked by first recognition — featured work, strong downloads, and growing visibility.',
                'starts_at'   => $breakthroughAt->toDateTimeString(),
                'ends_at'     => $breakthroughEnds?->toDateTimeString(),
                'is_current'  => false,
                'metadata'    => json_encode($this->eraMetadata($sorted, $breakthroughAt, $breakthroughEnds ?? $lastDate)),
                'created_at'  => $now->toDateTimeString(),
                'updated_at'  => $now->toDateTimeString(),
            ];
        }

        // ── Comeback Era ───────────────────────────────────────────────────
        if ($comebackDate !== null) {
            // Comeback era encompasses everything from the comeback to now (or next major event)
            $eras[] = [
                'user_id'     => $userId,
                'era_type'    => 'comeback',
                'title'       => 'Comeback Era',
                'description' => 'A return to creative work on Skinbase after a significant break.',
                'starts_at'   => $comebackDate->toDateTimeString(),
                'ends_at'     => null,
                'is_current'  => true,
                'metadata'    => json_encode($this->eraMetadata($sorted, $comebackDate, $lastDate)),
                'created_at'  => $now->toDateTimeString(),
                'updated_at'  => $now->toDateTimeString(),
            ];
        } else {
            // ── Current Era ───────────────────────────────────────────────
            // Only set if there's been activity in the last 2 years
            $twoYearsAgo = $now->copy()->subYears(2);

            if ($lastDate->greaterThanOrEqualTo($twoYearsAgo)) {
                $currentStart = $breakthroughAt ?? $firstDate;

                // Don't double-stamp if breakthrough era is already current
                if ($breakthroughAt === null || $currentStart->equalTo($firstDate)) {
                    $eras[] = [
                        'user_id'     => $userId,
                        'era_type'    => 'current',
                        'title'       => 'Current Era',
                        'description' => 'The latest active creative phase on Skinbase.',
                        'starts_at'   => $currentStart->toDateTimeString(),
                        'ends_at'     => null,
                        'is_current'  => true,
                        'metadata'    => json_encode($this->eraMetadata($sorted, $currentStart, $lastDate)),
                        'created_at'  => $now->toDateTimeString(),
                        'updated_at'  => $now->toDateTimeString(),
                    ];
                } else {
                    // Mark breakthrough as current
                    $lastIdx = count($eras) - 1;
                    $eras[$lastIdx]['is_current'] = true;
                    $eras[$lastIdx]['ends_at']    = null;
                }
            }
        }

        // Deduplicate: ensure we don't have two is_current=true if an era was edited above
        $currentCount = count(array_filter($eras, fn ($e) => $e['is_current']));
        if ($currentCount > 1) {
            // Only the last is_current one stays
            $found = false;
            for ($i = count($eras) - 1; $i >= 0; $i--) {
                if ($eras[$i]['is_current']) {
                    if ($found) {
                        $eras[$i]['is_current'] = false;
                    } else {
                        $found = true;
                    }
                }
            }
        }

        return $eras;
    }

    /**
     * @param  Collection<int, object>  $artworks
     */
    private function eraMetadata(Collection $artworks, CarbonInterface $from, CarbonInterface $to): array
    {
        $inRange = $artworks->filter(function (object $artwork) use ($from, $to): bool {
            $date = empty($artwork->published_at) ? null : Carbon::parse($artwork->published_at);

            if ($date === null) {
                return false;
            }

            return $date->greaterThanOrEqualTo($from) && $date->lessThanOrEqualTo($to);
        });

        $uploads = $inRange->count();
        $downloads = $inRange->sum(fn ($a): int => (int) ($a->stat_downloads ?? 0));

        $topArtwork = $inRange->sortByDesc(fn ($a): float => (float) ($a->stat_downloads ?? 0))->first();

        $years = $inRange
            ->map(fn ($a): int => (int) Carbon::parse($a->published_at)->year)
            ->unique()
            ->sort()
            ->values()
            ->all();

        return [
            'uploads_count'  => $uploads,
            'downloads'      => $downloads,
            'dominant_years' => $years,
            'top_artwork_id' => $topArtwork ? (int) $topArtwork->id : null,
        ];
    }

    private function firstFeaturedDate(int $userId): ?CarbonInterface
    {
        $row = DB::table('artwork_features as af')
            ->join('artworks as a', 'a.id', '=', 'af.artwork_id')
            ->where('a.user_id', $userId)
            ->whereNull('a.deleted_at')
            ->where('a.is_public', true)
            ->where('a.is_approved', true)
            ->whereNull('af.deleted_at')
            ->where('af.is_active', true)
            ->orderBy('af.featured_at')
            ->first(['af.featured_at']);

        return $row ? Carbon::parse($row->featured_at) : null;
    }

    /**
     * @param  Collection<int, object>  $sorted
     */
    private function firstMajorDownloadDate(Collection $sorted): ?CarbonInterface
    {
        // Threshold: artwork with 500+ downloads is considered a "major" milestone
        $artwork = $sorted->first(fn ($a): bool => (int) ($a->stat_downloads ?? 0) >= 500);

        return $artwork ? Carbon::parse($artwork->published_at) : null;
    }

    /**
     * @param  Collection<int, object>  $sorted
     */
    private function firstComebackDate(Collection $sorted): ?CarbonInterface
    {
        $prevDate = null;

        foreach ($sorted as $artwork) {
            $currentDate = Carbon::parse($artwork->published_at);

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

                if ($gapDays >= self::COMEBACK_GAP_DAYS) {
                    return $currentDate;
                }
            }

            $prevDate = $currentDate;
        }

        return null;
    }

    /**
     * @return array<string, mixed>
     */
    private function formatEra(CreatorEra $era): array
    {
        return [
            'type'        => $era->era_type,
            'title'       => $era->title,
            'description' => $era->description,
            'starts_at'   => $era->starts_at->toIso8601String(),
            'ends_at'     => $era->ends_at?->toIso8601String(),
            'is_current'  => $era->is_current,
            'stats'       => $era->metadata ?? [],
        ];
    }
}
