<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\Collection;
use App\Models\CollectionHistory;
use App\Models\User;
use App\Services\CollectionHealthService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Validation\ValidationException;

class CollectionHistoryService
{
    /**
     * @var array<string, array<int, string>>
     */
    private const RESTORABLE_FIELDS = [
        'updated' => ['title', 'visibility', 'lifecycle_state'],
        'workflow_updated' => ['workflow_state', 'program_key', 'partner_key', 'experiment_key', 'placement_eligibility'],
        'partner_program_metadata_updated' => ['partner_key', 'trust_tier', 'promotion_tier', 'sponsorship_state', 'ownership_domain', 'commercial_review_state', 'legal_review_state'],
    ];

    public function __construct(
        private readonly CollectionHealthService $health,
    ) {
    }

    public function record(Collection $collection, ?User $actor, string $actionType, ?string $summary = null, ?array $before = null, ?array $after = null): void
    {
        CollectionHistory::query()->create([
            'collection_id' => $collection->id,
            'actor_user_id' => $actor?->id,
            'action_type' => $actionType,
            'summary' => $summary,
            'before_json' => $before,
            'after_json' => $after,
            'created_at' => now(),
        ]);

        $collection->forceFill([
            'history_count' => (int) $collection->history_count + 1,
        ])->save();
    }

    public function historyFor(Collection $collection, int $perPage = 40): LengthAwarePaginator
    {
        return CollectionHistory::query()
            ->with('actor:id,username,name')
            ->where('collection_id', $collection->id)
            ->orderByDesc('created_at')
            ->paginate(max(10, min($perPage, 80)));
    }

    public function mapPaginator(LengthAwarePaginator $paginator): array
    {
        return [
            'data' => collect($paginator->items())->map(function (CollectionHistory $entry): array {
                $restorableFields = $this->restorablePayload($entry);

                return [
                    'id' => (int) $entry->id,
                    'action_type' => $entry->action_type,
                    'summary' => $entry->summary,
                    'before' => $entry->before_json,
                    'after' => $entry->after_json,
                    'can_restore' => $restorableFields !== [],
                    'restore_fields' => array_keys($restorableFields),
                    'created_at' => $entry->created_at?->toISOString(),
                    'actor' => $entry->actor ? [
                        'id' => (int) $entry->actor->id,
                        'username' => $entry->actor->username,
                        'name' => $entry->actor->name,
                    ] : null,
                ];
            })->values()->all(),
            'meta' => [
                'current_page' => $paginator->currentPage(),
                'last_page' => $paginator->lastPage(),
                'per_page' => $paginator->perPage(),
                'total' => $paginator->total(),
            ],
        ];
    }

    public function canRestore(CollectionHistory $entry): bool
    {
        return $this->restorablePayload($entry) !== [];
    }

    public function restore(Collection $collection, CollectionHistory $entry, ?User $actor = null): Collection
    {
        if ((int) $entry->collection_id !== (int) $collection->id) {
            throw ValidationException::withMessages([
                'history' => 'This history entry does not belong to the selected collection.',
            ]);
        }

        $payload = $this->restorablePayload($entry);

        if ($payload === []) {
            throw ValidationException::withMessages([
                'history' => 'This history entry cannot be restored safely.',
            ]);
        }

        $working = $collection->fresh();
        $before = [];

        foreach (array_keys($payload) as $field) {
            $before[$field] = $working->{$field};
        }

        $working->forceFill($payload);

        $healthPayload = $this->health->evaluate($working);

        $working->forceFill(array_merge($healthPayload, $payload, [
            'last_activity_at' => now(),
        ]))->save();

        $fresh = $working->fresh();

        $this->record(
            $fresh,
            $actor,
            'history_restored',
            sprintf('Collection restored from history entry #%d.', $entry->id),
            array_merge(['restored_history_id' => (int) $entry->id], $before),
            array_merge(['restored_history_id' => (int) $entry->id], $payload),
        );

        return $fresh;
    }

    /**
     * @return array<string, mixed>
     */
    private function restorablePayload(CollectionHistory $entry): array
    {
        $before = is_array($entry->before_json) ? $entry->before_json : [];
        $fields = self::RESTORABLE_FIELDS[$entry->action_type] ?? [];
        $payload = [];

        foreach ($fields as $field) {
            if (array_key_exists($field, $before)) {
                $payload[$field] = $before[$field];
            }
        }

        return $payload;
    }
}
