<?php

namespace App\Services;

use App\Enums\ModerationCaseStatus;
use App\Enums\ReportReason;
use App\Enums\ReportStatus;
use App\Enums\UserRole;
use App\Models\MealPosts;
use App\Models\ModerationCase;
use App\Models\PostReviews;
use App\Models\Report;
use App\Models\User;
use Filament\Notifications\Notification as FilamentNotification;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\DB;

class ModerationReportService
{
    public function report(Model $reportable, User $reporter, ReportReason $reason, ?string $details): Report
    {
        $this->abortIfInvalidTarget($reportable, $reporter);

        $shouldNotifyModerators = false;

        try {
            $report = DB::transaction(function () use ($reportable, $reporter, $reason, $details, &$shouldNotifyModerators): Report {
                $moderationCase = $this->findOrCreateModerationCase($reportable);

                abort_if(
                    Report::query()
                        ->where('reporter_id', $reporter->getKey())
                        ->where('reportable_type', $reportable::class)
                        ->where('reportable_id', $reportable->getKey())
                        ->exists(),
                    409,
                    __('api.reports.already_exists'),
                );

                $report = Report::create([
                    'moderation_case_id' => $moderationCase->getKey(),
                    'reporter_id' => $reporter->getKey(),
                    'reportable_type' => $reportable::class,
                    'reportable_id' => $reportable->getKey(),
                    'reason' => $reason,
                    'details' => $details,
                    'status' => ReportStatus::OPEN,
                ]);

                $reportsCount = $moderationCase->reports()->count();
                $updates = ['reports_count' => $reportsCount];

                if ($reportsCount >= $moderationCase->threshold && $moderationCase->threshold_notified_at === null) {
                    $updates['threshold_notified_at'] = now();
                    $shouldNotifyModerators = true;
                }

                $moderationCase->forceFill($updates)->save();

                return $report->load('moderationCase.caseable');
            });
        } catch (QueryException $exception) {
            if ($this->isDuplicateReportException($exception)) {
                abort(409, __('api.reports.already_exists'));
            }

            throw $exception;
        }

        if ($shouldNotifyModerators) {
            $this->notifyModerators($report->moderationCase);
        }

        return $report;
    }

    private function findOrCreateModerationCase(Model $reportable): ModerationCase
    {
        $moderationCase = ModerationCase::query()
            ->where('caseable_type', $reportable::class)
            ->where('caseable_id', $reportable->getKey())
            ->lockForUpdate()
            ->first();

        if ($moderationCase) {
            return $moderationCase;
        }

        return ModerationCase::create([
            'caseable_type' => $reportable::class,
            'caseable_id' => $reportable->getKey(),
            'status' => ModerationCaseStatus::OPEN,
            'threshold' => $this->thresholdFor($reportable),
        ]);
    }

    private function thresholdFor(Model $reportable): int
    {
        $thresholds = config('moderation.report_thresholds', []);

        return (int) ($thresholds[$reportable::class] ?? config('moderation.default_report_threshold', 3));
    }

    private function notifyModerators(ModerationCase $moderationCase): void
    {
        $freshCase = $moderationCase->fresh('caseable');

        if (! $freshCase) {
            return;
        }

        User::query()
            ->whereIn('role', [UserRole::ADMIN->value, UserRole::MODERATOR->value])
            ->get()
            ->whenNotEmpty(fn ($moderators) => FilamentNotification::make()
                ->title(__('api.notifications.moderation_threshold_title'))
                ->body(__('api.notifications.moderation_threshold_body', [
                    'type' => $freshCase->targetLabel(),
                    'count' => $freshCase->reports_count,
                ]))
                ->warning()
                ->sendToDatabase($moderators, isEventDispatched: true));
    }

    private function abortIfInvalidTarget(Model $reportable, User $reporter): void
    {
        abort_if(
            $reportable instanceof User && $reportable->is($reporter),
            422,
            __('api.reports.cannot_report_self'),
        );

        abort_if(
            $reportable instanceof MealPosts && $reportable->user_id === $reporter->getKey(),
            422,
            __('api.reports.cannot_report_own_meal'),
        );

        abort_if(
            $reportable instanceof PostReviews && $reportable->user_id === $reporter->getKey(),
            422,
            __('api.reports.cannot_report_own_review'),
        );
    }

    private function isDuplicateReportException(QueryException $exception): bool
    {
        return $exception->getCode() === '23000'
            && str_contains($exception->getMessage(), 'reports_reporter_reportable_unique');
    }
}
