<?php

declare(strict_types=1);

namespace App\Services\NovaCards;

use App\Models\NovaCard;
use App\Models\User;

class NovaCardPublishModerationService
{
    private const REASON_LABELS = [
        'duplicate_content' => 'Duplicate content',
        'self_remix_loop' => 'Self-remix loop',
    ];

    public const DISPOSITION_LABELS = [
        'cleared_after_review' => 'Cleared after review',
        'approved_with_watch' => 'Approved with watch',
        'escalated_for_review' => 'Escalated for review',
        'rights_review_required' => 'Rights review required',
        'rejected_after_review' => 'Rejected after review',
        'returned_to_pending' => 'Returned to pending',
    ];

    public function evaluate(NovaCard $card): array
    {
        $card->loadMissing(['originalCard.user', 'rootCard.user']);

        $reasons = [];

        if ($this->hasDuplicateContent($card)) {
            $reasons[] = 'duplicate_content';
        }

        if ($this->hasSelfRemixLoop($card)) {
            $reasons[] = 'self_remix_loop';
        }

        return [
            'flagged' => $reasons !== [],
            'reasons' => $reasons,
        ];
    }

    public function moderationStatus(NovaCard $card): string
    {
        return $this->evaluate($card)['flagged'] ? NovaCard::MOD_FLAGGED : NovaCard::MOD_APPROVED;
    }

    public function applyPublishOutcome(NovaCard $card, array $evaluation): NovaCard
    {
        $project = (array) ($card->project_json ?? []);
        $project['moderation'] = [
            'source' => 'publish_heuristics',
            'flagged' => (bool) ($evaluation['flagged'] ?? false),
            'reasons' => $this->normalizeReasons($evaluation['reasons'] ?? []),
            'updated_at' => now()->toISOString(),
        ];

        $card->forceFill([
            'project_json' => $project,
            'status' => NovaCard::STATUS_PUBLISHED,
            'moderation_status' => (bool) ($evaluation['flagged'] ?? false) ? NovaCard::MOD_FLAGGED : NovaCard::MOD_APPROVED,
        ])->save();

        return $card->refresh();
    }

    public function storedReasons(NovaCard $card): array
    {
        return $this->normalizeReasons(((array) (($card->project_json ?? [])['moderation'] ?? []))['reasons'] ?? []);
    }

    public function storedReasonLabels(NovaCard $card): array
    {
        return $this->labelsFor($this->storedReasons($card));
    }

    public function storedSource(NovaCard $card): ?string
    {
        $source = ((array) (($card->project_json ?? [])['moderation'] ?? []))['source'] ?? null;

        return is_string($source) && $source !== '' ? $source : null;
    }

    public function latestOverride(NovaCard $card): ?array
    {
        $override = ((array) (($card->project_json ?? [])['moderation'] ?? []))['override'] ?? null;

        return is_array($override) && $override !== [] ? $override : null;
    }

    public function dispositionOptions(?string $moderationStatus = null): array
    {
        $keys = match ($moderationStatus) {
            NovaCard::MOD_APPROVED => ['cleared_after_review', 'approved_with_watch'],
            NovaCard::MOD_FLAGGED => ['escalated_for_review', 'rights_review_required'],
            NovaCard::MOD_REJECTED => ['rejected_after_review'],
            NovaCard::MOD_PENDING => ['returned_to_pending'],
            default => array_keys(self::DISPOSITION_LABELS),
        };

        return array_values(array_map(fn (string $key): array => [
            'value' => $key,
            'label' => self::DISPOSITION_LABELS[$key] ?? ucwords(str_replace('_', ' ', $key)),
        ], $keys));
    }

    public function overrideHistory(NovaCard $card): array
    {
        $history = ((array) (($card->project_json ?? [])['moderation'] ?? []))['override_history'] ?? [];

        return array_values(array_filter($history, fn ($entry): bool => is_array($entry) && $entry !== []));
    }

    public function recordStaffOverride(
        NovaCard $card,
        string $moderationStatus,
        ?User $actor,
        string $source,
        array $context = [],
    ): NovaCard {
        $project = (array) ($card->project_json ?? []);
        $moderation = (array) ($project['moderation'] ?? []);
        $disposition = $this->normalizeDisposition(
            $context['disposition'] ?? $this->defaultDispositionForStatus($moderationStatus),
            $moderationStatus,
        );
        $override = array_filter([
            'moderation_status' => $moderationStatus,
            'previous_status' => (string) $card->moderation_status,
            'disposition' => $disposition,
            'disposition_label' => self::DISPOSITION_LABELS[$disposition] ?? ucwords(str_replace('_', ' ', $disposition)),
            'source' => $source,
            'actor_user_id' => $actor?->id,
            'actor_username' => $actor?->username,
            'note' => isset($context['note']) && is_string($context['note']) && trim($context['note']) !== '' ? trim($context['note']) : null,
            'report_id' => isset($context['report_id']) ? (int) $context['report_id'] : null,
            'updated_at' => now()->toISOString(),
        ], fn ($value): bool => $value !== null);

        $history = $this->overrideHistory($card);
        array_unshift($history, $override);

        $moderation['override'] = $override;
        $moderation['override_history'] = array_slice($history, 0, 10);
        $project['moderation'] = $moderation;

        $card->forceFill([
            'moderation_status' => $moderationStatus,
            'project_json' => $project,
        ])->save();

        return $card->refresh();
    }

    public function labelsFor(array $reasons): array
    {
        return array_values(array_map(
            fn (string $reason): string => self::REASON_LABELS[$reason] ?? ucwords(str_replace('_', ' ', $reason)),
            $this->normalizeReasons($reasons),
        ));
    }

    private function normalizeReasons(array $reasons): array
    {
        return array_values(array_unique(array_filter(array_map(
            fn ($reason): string => is_string($reason) ? trim($reason) : '',
            $reasons,
        ))));
    }

    private function normalizeDisposition(mixed $disposition, string $moderationStatus): string
    {
        $value = is_string($disposition) ? trim($disposition) : '';

        return $value !== '' && array_key_exists($value, self::DISPOSITION_LABELS)
            ? $value
            : $this->defaultDispositionForStatus($moderationStatus);
    }

    private function defaultDispositionForStatus(string $moderationStatus): string
    {
        return match ($moderationStatus) {
            NovaCard::MOD_APPROVED => 'cleared_after_review',
            NovaCard::MOD_FLAGGED => 'escalated_for_review',
            NovaCard::MOD_REJECTED => 'rejected_after_review',
            default => 'returned_to_pending',
        };
    }

    private function hasDuplicateContent(NovaCard $card): bool
    {
        $title = mb_strtolower(trim((string) $card->title));
        $quote = mb_strtolower(trim((string) $card->quote_text));

        if ($title === '' || $quote === '') {
            return false;
        }

        return NovaCard::query()
            ->where('id', '!=', $card->id)
            ->where('status', NovaCard::STATUS_PUBLISHED)
            ->whereNotIn('moderation_status', [NovaCard::MOD_FLAGGED, NovaCard::MOD_REJECTED])
            ->whereRaw('LOWER(title) = ?', [$title])
            ->whereRaw('LOWER(quote_text) = ?', [$quote])
            ->exists();
    }

    private function hasSelfRemixLoop(NovaCard $card): bool
    {
        if (! $card->originalCard || ! $card->rootCard) {
            return false;
        }

        $depth = 0;
        $cursor = $card;
        $visited = [];

        while ($cursor->originalCard && ! in_array($cursor->id, $visited, true)) {
            $visited[] = $cursor->id;
            $cursor = $cursor->originalCard;
            $depth++;
        }

        return $depth >= 3
            && (int) $card->user_id === (int) ($card->originalCard->user_id ?? 0)
            && (int) $card->user_id === (int) ($card->rootCard->user_id ?? 0);
    }
}