<?php

declare(strict_types=1);

namespace App\Services\Maturity;

use App\Models\Artwork;
use App\Models\ArtworkMaturityAuditFinding;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;

final class ArtworkMaturityAuditService
{
    public function eligibleArtworkQuery(bool $includeExistingOpenFindings = false): Builder
    {
        $query = Artwork::query()
            ->whereNotNull('hash')
            ->whereNotNull('thumb_ext')
            ->whereRaw('TRIM(hash) != ?',[ '' ])
            ->whereRaw('TRIM(thumb_ext) != ?',[ '' ]);

        $this->applyLegacyUnsetFilter($query);

        if (! $includeExistingOpenFindings && Schema::hasTable('artwork_maturity_audit_findings')) {
            $query->whereDoesntHave('maturityAuditFinding', function (Builder $finding): void {
                $finding->where('status', ArtworkMaturityAuditFinding::STATUS_OPEN);
            });
        }

        return $query;
    }

    public function openFindingsQuery(): Builder
    {
        return ArtworkMaturityAuditFinding::query()
            ->with(['artwork.user.profile', 'artwork.group', 'artwork.categories.contentType'])
            ->where('status', ArtworkMaturityAuditFinding::STATUS_OPEN)
            ->whereHas('artwork', function (Builder $query): void {
                $this->applyLegacyUnsetFilter($query);
            });
    }

    public function openFindingsCount(): int
    {
        if (! Schema::hasTable('artwork_maturity_audit_findings')) {
            return 0;
        }

        return (int) $this->openFindingsQuery()->count();
    }

    public function isArtworkEligible(Artwork $artwork): bool
    {
        return ! (bool) $artwork->is_mature
            && in_array((string) ($artwork->maturity_level ?? ArtworkMaturityService::LEVEL_SAFE), ['', ArtworkMaturityService::LEVEL_SAFE], true)
            && in_array((string) ($artwork->maturity_status ?? ArtworkMaturityService::STATUS_CLEAR), ['', ArtworkMaturityService::STATUS_CLEAR], true)
            && in_array((string) ($artwork->maturity_source ?? ArtworkMaturityService::SOURCE_LEGACY), ['', ArtworkMaturityService::SOURCE_LEGACY], true)
            && $artwork->maturity_declared_at === null
            && $artwork->maturity_reviewed_at === null;
    }

    /**
     * @param array<string, mixed> $assessment
     */
    public function shouldOpenFinding(array $assessment): bool
    {
        $status = Str::lower(trim((string) ($assessment['status'] ?? ArtworkMaturityService::AI_STATUS_FAILED)));
        if ($status !== ArtworkMaturityService::AI_STATUS_SUCCEEDED) {
            return false;
        }

        $actionHint = Str::lower(trim((string) ($assessment['action_hint'] ?? '')));
        if (in_array($actionHint, [ArtworkMaturityService::AI_ACTION_REVIEW, ArtworkMaturityService::AI_ACTION_FLAG_HIGH], true)) {
            return true;
        }

        $label = Str::lower(trim((string) ($assessment['maturity_label'] ?? '')));
        $confidence = is_numeric($assessment['confidence'] ?? null) ? (float) $assessment['confidence'] : 0.0;

        return $label === ArtworkMaturityService::LEVEL_MATURE
            && $confidence >= (float) config('maturity.ai.threshold', 0.68);
    }

    /**
     * @param array<string, mixed> $assessment
     */
    public function recordFinding(Artwork $artwork, array $assessment, string $thumbnailVariant): ArtworkMaturityAuditFinding
    {
        $finding = ArtworkMaturityAuditFinding::query()->updateOrCreate(
            ['artwork_id' => (int) $artwork->id],
            [
                'status' => ArtworkMaturityAuditFinding::STATUS_OPEN,
                'thumbnail_variant' => $thumbnailVariant,
                'ai_label' => $this->nullableLowerString($assessment['maturity_label'] ?? null),
                'ai_confidence' => $this->nullableFloat($assessment['confidence'] ?? null),
                'ai_score' => $this->nullableFloat($assessment['score'] ?? ($assessment['confidence'] ?? null)),
                'ai_labels' => $this->normalizeLabels($assessment['labels'] ?? []),
                'ai_model' => $this->nullableString($assessment['model'] ?? null),
                'ai_threshold_used' => $this->nullableFloat($assessment['threshold_used'] ?? null),
                'ai_analysis_time_ms' => is_numeric($assessment['analysis_time_ms'] ?? null) ? (int) $assessment['analysis_time_ms'] : null,
                'ai_action_hint' => $this->nullableLowerString($assessment['action_hint'] ?? null),
                'ai_status' => $this->nullableLowerString($assessment['status'] ?? ArtworkMaturityService::AI_STATUS_FAILED) ?? ArtworkMaturityService::AI_STATUS_FAILED,
                'ai_advisory' => $this->nullableString($assessment['advisory'] ?? null),
                'detected_at' => now(),
                'last_scanned_at' => now(),
                'resolution_action' => null,
                'resolution_note' => null,
                'resolved_by' => null,
                'resolved_at' => null,
            ],
        );

        return $finding->fresh(['artwork']);
    }

    public function markFindingCleared(Artwork $artwork, ?string $note = null): void
    {
        ArtworkMaturityAuditFinding::query()
            ->where('artwork_id', (int) $artwork->id)
            ->where('status', ArtworkMaturityAuditFinding::STATUS_OPEN)
            ->update([
                'status' => ArtworkMaturityAuditFinding::STATUS_CLEARED,
                'resolution_action' => 'auto_cleared',
                'resolution_note' => $note,
                'resolved_at' => now(),
                'last_scanned_at' => now(),
            ]);
    }

    public function resolveFindingForReview(Artwork $artwork, Authenticatable $moderator, string $action, ?string $note = null): void
    {
        $moderatorId = (int) $moderator->getAuthIdentifier();

        ArtworkMaturityAuditFinding::query()
            ->where('artwork_id', (int) $artwork->id)
            ->where('status', ArtworkMaturityAuditFinding::STATUS_OPEN)
            ->update([
                'status' => ArtworkMaturityAuditFinding::STATUS_REVIEWED,
                'resolution_action' => Str::lower(trim($action)),
                'resolution_note' => $note,
                'resolved_by' => $moderatorId,
                'resolved_at' => now(),
                'last_scanned_at' => now(),
            ]);
    }

    private function applyLegacyUnsetFilter(Builder $query): Builder
    {
        return $query
            ->where(function (Builder $builder): void {
                $builder->whereNull('maturity_declared_at')
                    ->whereNull('maturity_reviewed_at')
                    ->where(function (Builder $state): void {
                        $state->whereNull('maturity_source')
                            ->orWhere('maturity_source', ArtworkMaturityService::SOURCE_LEGACY);
                    })
                    ->where(function (Builder $state): void {
                        $state->whereNull('maturity_status')
                            ->orWhere('maturity_status', ArtworkMaturityService::STATUS_CLEAR);
                    })
                    ->where(function (Builder $state): void {
                        $state->whereNull('maturity_level')
                            ->orWhere('maturity_level', ArtworkMaturityService::LEVEL_SAFE);
                    })
                    ->where(function (Builder $state): void {
                        $state->whereNull('is_mature')
                            ->orWhere('is_mature', false);
                    });
            });
    }

    /**
     * @param mixed $value
     */
    private function nullableFloat(mixed $value): ?float
    {
        return is_numeric($value) ? (float) $value : null;
    }

    /**
     * @param mixed $value
     */
    private function nullableString(mixed $value): ?string
    {
        $resolved = trim((string) $value);

        return $resolved !== '' ? $resolved : null;
    }

    /**
     * @param mixed $value
     */
    private function nullableLowerString(mixed $value): ?string
    {
        $resolved = $this->nullableString($value);

        return $resolved !== null ? Str::lower($resolved) : null;
    }

    /**
     * @param mixed $value
     * @return list<string>|null
     */
    private function normalizeLabels(mixed $value): ?array
    {
        if (! is_array($value)) {
            return null;
        }

        $labels = array_values(array_filter(array_map(
            static fn (mixed $label): string => Str::lower(trim((string) $label)),
            $value,
        )));

        return $labels !== [] ? array_values(array_unique($labels)) : null;
    }
}