<?php

namespace App\Services\Moderation;

use App\Enums\ModerationActionType;
use App\Enums\ModerationDomainStatus;
use App\Models\ContentModerationActionLog;
use App\Models\ContentModerationDomain;
use App\Models\ContentModerationFinding;
use App\Models\User;

class DomainReputationService
{
    public function __construct(
        private readonly DomainIntelligenceService $intelligence,
    ) {
    }

    public function normalizeDomain(?string $domain): ?string
    {
        if (! is_string($domain)) {
            return null;
        }

        $normalized = trim(mb_strtolower($domain));
        $normalized = preg_replace('/^www\./', '', $normalized);

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

    public function statusForDomain(string $domain): ModerationDomainStatus
    {
        $normalized = $this->normalizeDomain($domain);
        if ($normalized === null) {
            return ModerationDomainStatus::Neutral;
        }

        $record = ContentModerationDomain::query()->where('domain', $normalized)->first();
        if ($record !== null) {
            return $record->status;
        }

        if ($this->matchesAnyPattern($normalized, (array) \app('config')->get('content_moderation.allowed_domains', []))) {
            return ModerationDomainStatus::Allowed;
        }

        if ($this->matchesAnyPattern($normalized, (array) \app('config')->get('content_moderation.blacklisted_domains', []))) {
            return ModerationDomainStatus::Blocked;
        }

        if ($this->matchesAnyPattern($normalized, (array) \app('config')->get('content_moderation.suspicious_domains', []))) {
            return ModerationDomainStatus::Suspicious;
        }

        return ModerationDomainStatus::Neutral;
    }

    /**
     * @param array<int, string> $domains
     * @return array<int, ContentModerationDomain>
     */
    public function trackDomains(array $domains, bool $flagged = false, bool $confirmedSpam = false): array
    {
        $normalized = \collect($domains)
            ->map(fn (?string $domain): ?string => $this->normalizeDomain($domain))
            ->filter()
            ->unique()
            ->values();

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

        $existing = ContentModerationDomain::query()
            ->whereIn('domain', $normalized->all())
            ->get()
            ->keyBy('domain');

        $records = [];
        $now = \now();

        foreach ($normalized as $domain) {
            $defaultStatus = $this->statusForDomain($domain);
            $record = $existing[$domain] ?? new ContentModerationDomain([
                'domain' => $domain,
                'status' => $defaultStatus,
                'first_seen_at' => $now,
            ]);

            $record->forceFill([
                'status' => $record->status ?? $defaultStatus,
                'times_seen' => ((int) $record->times_seen) + 1,
                'times_flagged' => ((int) $record->times_flagged) + ($flagged ? 1 : 0),
                'times_confirmed_spam' => ((int) $record->times_confirmed_spam) + ($confirmedSpam ? 1 : 0),
                'first_seen_at' => $record->first_seen_at ?? $now,
                'last_seen_at' => $now,
            ])->save();

            $records[] = $record->fresh();
        }

        return $records;
    }

    public function updateStatus(string $domain, ModerationDomainStatus $status, ?User $actor = null, ?string $notes = null): ContentModerationDomain
    {
        $normalized = $this->normalizeDomain($domain);
        \abort_unless($normalized !== null, 422, 'Invalid domain.');

        $record = ContentModerationDomain::query()->firstOrNew(['domain' => $normalized]);
        $previous = $record->status?->value;

        $record->forceFill([
            'status' => $status,
            'first_seen_at' => $record->first_seen_at ?? \now(),
            'last_seen_at' => \now(),
            'notes' => $notes !== null && trim($notes) !== '' ? trim($notes) : $record->notes,
        ])->save();

        ContentModerationActionLog::query()->create([
            'target_type' => 'domain',
            'target_id' => $record->id,
            'action_type' => match ($status) {
                ModerationDomainStatus::Blocked => ModerationActionType::BlockDomain->value,
                ModerationDomainStatus::Suspicious => ModerationActionType::MarkDomainSuspicious->value,
                ModerationDomainStatus::Escalated => ModerationActionType::Escalate->value,
                ModerationDomainStatus::ReviewRequired => ModerationActionType::MarkDomainSuspicious->value,
                ModerationDomainStatus::Allowed, ModerationDomainStatus::Neutral => ModerationActionType::AllowDomain->value,
            },
            'actor_type' => $actor ? 'admin' : 'system',
            'actor_id' => $actor?->id,
            'notes' => $notes,
            'old_status' => $previous,
            'new_status' => $status->value,
            'meta_json' => ['domain' => $normalized],
            'created_at' => \now(),
        ]);

        $this->intelligence->refreshDomain($normalized);

        return $record->fresh();
    }

    /**
     * @return array<int, string>
     */
    public function shortenerDomains(): array
    {
        return \collect((array) \app('config')->get('content_moderation.shortener_domains', []))
            ->map(fn (string $domain): ?string => $this->normalizeDomain($domain))
            ->filter()
            ->values()
            ->all();
    }

    public function attachDomainIds(ContentModerationFinding $finding): void
    {
        $domains = \collect((array) $finding->matched_domains_json)
            ->map(fn (?string $domain): ?string => $this->normalizeDomain($domain))
            ->filter()
            ->unique()
            ->values();

        if ($domains->isEmpty()) {
            $finding->forceFill(['domain_ids_json' => []])->save();
            return;
        }

        $ids = ContentModerationDomain::query()
            ->whereIn('domain', $domains->all())
            ->pluck('id')
            ->map(static fn (int $id): int => $id)
            ->values()
            ->all();

        $finding->forceFill(['domain_ids_json' => $ids])->save();

        foreach ($domains as $domain) {
            $this->intelligence->refreshDomain((string) $domain);
        }
    }

    private function matchesAnyPattern(string $domain, array $patterns): bool
    {
        foreach ($patterns as $pattern) {
            $pattern = trim(mb_strtolower((string) $pattern));
            if ($pattern === '') {
                continue;
            }

            $quoted = preg_quote($pattern, '/');
            $regex = '/^' . str_replace('\\*', '.*', $quoted) . '$/i';
            if (preg_match($regex, $domain) === 1) {
                return true;
            }
        }

        return false;
    }
}