<?php

namespace App\Services\Task;

use App\Models\Task\Task;
use App\Models\Task\TaskAssignmentRule;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

/**
 * Otomatik görev motoru. Domain olayları (event kancaları) ve zamanlanmış
 * tarayıcı, görev üretmek için bu servisi çağırır. Kural (TaskAssignmentRule)
 * kapalıysa veya hedef kullanıcı çözülemezse hiçbir şey yapılmaz.
 *
 * Dedupe: tasks.automation_key üzerinde (agency_id ile birlikte) unique index —
 * aynı olay aynı kayıt için ömür boyu tek görev üretir. Tekrarlanabilir olaylar
 * (örn. her ödeme) context['dedupe_suffix'] ile ayrışır.
 */
class AutoTaskService
{
    /**
     * fire() ile aynı; ancak hiçbir zaman exception fırlatmaz.
     * Domain akışlarını (satış kaydı, operasyon geçişleri) asla kırmamalıdır.
     */
    public function fireSafe(string $key, Model $source, array $context = [], array|\Closure $extraRelations = [], ?int $agencyId = null): ?Task
    {
        try {
            return $this->fire($key, $source, $context, $extraRelations, $agencyId);
        } catch (\Throwable $e) {
            Log::warning('AutoTask fire hatası', [
                'trigger' => $key,
                'source' => get_class($source) . '#' . $source->getKey(),
                'error' => $e->getMessage(),
            ]);

            return null;
        }
    }

    /**
     * Tetikleyiciyi ateşler; kural açıksa görev(ler) oluşturur.
     *
     * @param array|\Closure $extraRelations Ek ilişkili kayıtlar (Model listesi).
     *                                       Closure verilirse yalnızca kural açıkken çalıştırılır.
     */
    public function fire(string $key, Model $source, array $context = [], array|\Closure $extraRelations = [], ?int $agencyId = null): ?Task
    {
        $def = TaskTriggerCatalog::get($key);
        if (! $def) {
            return null;
        }

        $agencyId = $agencyId
            ?? ($context['agency_id'] ?? null)
            ?? ($source->agency_id ?? null);

        if (! $agencyId) {
            return null;
        }

        $rule = TaskAssignmentRule::byAgency((int) $agencyId)
            ->where('trigger_key', $key)
            ->first();

        if (! $rule || ! $rule->is_enabled) {
            return null;
        }

        $userIds = $rule->resolveUserIds();
        if (empty($userIds)) {
            return null;
        }

        if ($extraRelations instanceof \Closure) {
            $extraRelations = (array) $extraRelations();
        }

        $automationKey = $key . ':' . get_class($source) . ':' . $source->getKey();
        if (! empty($context['dedupe_suffix'])) {
            $automationKey .= ':' . $context['dedupe_suffix'];
        }

        // Şablon değişkenleri: ilişkili kayıtların verilerinden (müşteri, satış, kalkış…)
        $values = TaskTemplateVariables::values($key, $source, $extraRelations, $context, (int) $agencyId);

        // Acente özel şablon tanımladıysa o kullanılır; yoksa katalog varsayılanı.
        // Özel açıklama zengin metin (HTML) olduğundan değerler escape edilerek gömülür.
        $title = $this->renderTemplate($rule->title_template ?: $def['title_template'], $values);
        $description = $rule->description_template
            ? $this->renderTemplate($rule->description_template, array_map(fn ($v) => e($v), $values))
            : $def['description'];

        $priority = $rule->priority_override ?: $def['default_priority'];
        $offsetHours = $rule->due_offset_hours_override ?? $def['due_offset_hours'];
        $dueAt = $context['due_at'] ?? now()->addHours((int) $offsetHours);

        $payload = [
            'agency_id' => (int) $agencyId,
            'title' => $title !== '' ? $title : $def['label'],
            'description' => $description,
            'priority' => $priority,
            'due_date' => $dueAt,
            'is_auto' => true,
            'trigger_key' => $key,
            'context' => collect($context)->except(['agency_id', 'due_at', 'dedupe_suffix'])->all(),
        ];

        // Checklist: kural null ise katalog varsayılanı; kural [] ise checklist istenmiyor
        $checklist = $rule->checklist;
        if ($checklist === null) {
            $checklist = $def['default_checklist'] ?? [];
        }
        $checklist = collect($checklist)
            ->map(fn ($t) => trim((string) $t))
            ->filter()
            ->take(30)
            ->values()
            ->all();

        if ($rule->assignment_mode === TaskAssignmentRule::MODE_INDIVIDUAL) {
            $created = null;
            foreach ($userIds as $userId) {
                $created = $this->createDeduped(
                    $payload,
                    $automationKey . ':u' . $userId,
                    [$userId],
                    $source,
                    $extraRelations,
                    $checklist
                ) ?? $created;
            }

            return $created;
        }

        return $this->createDeduped($payload, $automationKey, $userIds, $source, $extraRelations, $checklist);
    }

    /** Unique index + ön kontrol ile duplicate-safe görev oluşturur. */
    private function createDeduped(array $payload, string $automationKey, array $userIds, Model $source, array $extraRelations, array $checklist = []): ?Task
    {
        $exists = Task::withTrashed()
            ->where('agency_id', $payload['agency_id'])
            ->where('automation_key', $automationKey)
            ->exists();

        if ($exists) {
            return null;
        }

        try {
            return DB::transaction(function () use ($payload, $automationKey, $userIds, $source, $extraRelations, $checklist) {
                $task = Task::create($payload + ['automation_key' => $automationKey, 'status' => Task::STATUS_PENDING]);

                // Kontrol listesi maddeleri görevle birlikte oluşur
                foreach ($checklist as $i => $itemTitle) {
                    $task->checklistItems()->create([
                        'title' => mb_substr($itemTitle, 0, 200),
                        'sort_order' => $i,
                    ]);
                }

                $task->assignees()->attach(
                    collect($userIds)->mapWithKeys(fn ($id) => [$id => ['role' => 'assignee']])->all()
                );

                $task->relations()->create([
                    'related_type' => get_class($source),
                    'related_id' => $source->getKey(),
                    'is_primary' => true,
                ]);

                foreach ($extraRelations as $related) {
                    if (! $related instanceof Model || ! $related->getKey()) {
                        continue;
                    }
                    if (get_class($related) === get_class($source) && $related->getKey() === $source->getKey()) {
                        continue;
                    }
                    $task->relations()->firstOrCreate([
                        'related_type' => get_class($related),
                        'related_id' => $related->getKey(),
                    ]);
                }

                return $task;
            });
        } catch (QueryException $e) {
            // Unique index yarışı: aynı olay eşzamanlı iki kez ateşlendi — sessizce yut
            return null;
        }
    }

    /** Şablondaki {{token}} değişkenlerini değer setinden doldurur, bilinmeyenleri temizler. */
    private function renderTemplate(string $template, array $values): string
    {
        $out = preg_replace_callback(
            '/\{\{\s*([a-z0-9_.]+)\s*\}\}/iu',
            fn ($m) => $values[mb_strtolower($m[1])] ?? '',
            $template
        );

        return trim(preg_replace('/[ \t]{2,}/', ' ', $out));
    }
}
