<?php

namespace App\Http\Controllers\Agency\Mail;

use App\Http\Controllers\Controller;
use App\Models\Mail\MailAccount;
use App\Models\Mail\MailBlockedSender;
use App\Models\Mail\MailFolder;
use App\Models\Mail\MailMessage;
use App\Models\Mail\MailRule;
use App\Services\Mail\MailSpamService;
use App\Services\Mail\MailSyncService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

/**
 * Gelen postaya otomatik uygulanan filtre kuralları.
 *
 * Kural motoru senkronun içinde çalışır (MailSyncService::applyRules); burası
 * yalnızca kuralların tanımlanmasını sağlar: koşullar (kimden / kime / konu /
 * içerik) ve eylemler (klasöre taşı, okundu işaretle, yıldızla, çöpe at).
 *
 * Kurallar acenteye aittir; bir hesaba bağlanabilir ya da tüm hesaplara
 * uygulanabilir (account_id null).
 */
class MailRuleController extends Controller
{
    /** Bir kuralda tanımlanabilecek azami koşul sayısı. */
    private const MAX_CONDITIONS = 8;

    public function __construct(private MailSyncService $sync) {}

    private function agencyId(): int
    {
        return (int) (Auth::user()?->currentAgencyId() ?? 0);
    }

    /** Kullanıcının görebileceği hesaplar: acente geneli + kendi hesapları. */
    private function accountsQuery()
    {
        $userId = (int) Auth::id();

        return MailAccount::where('agency_id', $this->agencyId())
            ->where(fn ($q) => $q->whereNull('user_id')->orWhere('user_id', $userId))
            ->orderBy('sort_order')
            ->orderBy('id');
    }

    private function accountIds(): array
    {
        return $this->accountsQuery()->pluck('id')->map(fn ($v) => (int) $v)->all();
    }

    private function findRule(int $id): MailRule
    {
        $rule = MailRule::where('agency_id', $this->agencyId())->find($id);
        abort_if(! $rule, 404, 'Kural bulunamadı.');

        // Belirli bir hesaba bağlı kural, o hesabı göremeyen kullanıcıya kapalı
        if ($rule->account_id && ! in_array((int) $rule->account_id, $this->accountIds(), true)) {
            abort(404, 'Kural bulunamadı.');
        }

        return $rule;
    }

    // ─────────────────────────────────────────── okuma

    /** Kural listesi + form için gereken seçenekler. */
    public function index(): JsonResponse
    {
        $accountIds = $this->accountIds();

        $rules = MailRule::where('agency_id', $this->agencyId())
            ->where(fn ($q) => $q->whereNull('account_id')->orWhereIn('account_id', $accountIds ?: [0]))
            ->orderBy('sort_order')
            ->orderBy('id')
            ->get();

        return response()->json([
            'rules' => $rules->map(fn (MailRule $r) => $this->payload($r))->values(),
            'accounts' => $this->accountsQuery()->get()->map(fn (MailAccount $a) => [
                'id' => $a->id,
                'name' => $a->name,
                'email' => $a->email,
                'color' => $a->color,
            ])->values(),
            'targets' => $this->targets($accountIds),
            'fields' => [
                ['key' => 'from', 'label' => 'Gönderen'],
                ['key' => 'to', 'label' => 'Alıcı'],
                ['key' => 'subject', 'label' => 'Konu'],
                ['key' => 'body', 'label' => 'İçerik'],
            ],
            'operators' => [
                ['key' => 'contains', 'label' => 'içeriyorsa'],
                ['key' => 'equals', 'label' => 'eşitse'],
                ['key' => 'starts', 'label' => 'ile başlıyorsa'],
                ['key' => 'ends', 'label' => 'ile bitiyorsa'],
            ],
        ]);
    }

    /**
     * Taşıma hedefleri: önce standart klasör tipleri (hesaptan bağımsız
     * çalışır), ardından hesapların kendi özel klasörleri.
     */
    private function targets(array $accountIds): array
    {
        $standard = [
            ['value' => 'archive', 'label' => 'Arşiv', 'group' => 'Standart klasörler'],
            ['value' => 'junk', 'label' => 'Gereksiz E-posta', 'group' => 'Standart klasörler'],
            ['value' => 'trash', 'label' => 'Silinmiş Öğeler', 'group' => 'Standart klasörler'],
        ];

        $custom = MailFolder::whereIn('account_id', $accountIds ?: [0])
            ->where('type', 'custom')
            ->with('account:id,name')
            ->orderBy('name')
            ->get()
            ->map(fn (MailFolder $f) => [
                'value' => (string) $f->id,
                'label' => $f->name,
                'group' => $f->account?->name ?: 'Klasörler',
            ])
            ->values()
            ->all();

        return array_merge($standard, $custom);
    }

    private function payload(MailRule $rule): array
    {
        $actions = $rule->actions_json ?: [];

        return [
            'id' => $rule->id,
            'name' => $rule->name,
            'account_id' => $rule->account_id,
            'is_active' => (bool) $rule->is_active,
            'match_type' => $rule->match_type,
            'conditions' => array_values($rule->conditions_json ?: []),
            'actions' => [
                'move_to' => $actions['move_to'] ?? '',
                'mark_read' => (bool) ($actions['mark_read'] ?? false),
                'flag' => (bool) ($actions['flag'] ?? false),
                'delete' => (bool) ($actions['delete'] ?? false),
            ],
            'stop_processing' => (bool) $rule->stop_processing,
            'sort_order' => (int) $rule->sort_order,
            'summary' => $this->summary($rule),
        ];
    }

    /** Kuralı tek satırda anlatır — listede okunur kalsın. */
    private function summary(MailRule $rule): string
    {
        $fields = ['from' => 'Gönderen', 'to' => 'Alıcı', 'subject' => 'Konu', 'body' => 'İçerik'];
        $ops = ['contains' => 'içeriyorsa', 'equals' => 'eşitse', 'starts' => 'ile başlıyorsa', 'ends' => 'ile bitiyorsa'];

        $parts = collect($rule->conditions_json ?: [])
            ->map(fn ($c) => trim(
                ($fields[$c['field'] ?? ''] ?? '?').' "'.($c['value'] ?? '').'" '.($ops[$c['op'] ?? 'contains'] ?? '')
            ))
            ->implode($rule->match_type === 'any' ? ' veya ' : ' ve ');

        $actions = $rule->actions_json ?: [];
        $done = collect([
            ! empty($actions['move_to']) ? 'klasöre taşı' : null,
            ! empty($actions['mark_read']) ? 'okundu işaretle' : null,
            ! empty($actions['flag']) ? 'yıldızla' : null,
            ! empty($actions['delete']) ? 'çöpe at' : null,
        ])->filter()->implode(', ');

        return $parts.($done !== '' ? ' → '.$done : '');
    }

    // ─────────────────────────────────────────── yazma

    public function store(Request $request): JsonResponse
    {
        $data = $this->validated($request);

        $rule = new MailRule($data);
        $rule->agency_id = $this->agencyId();
        $rule->save();

        return response()->json(['ok' => true, 'rule' => $this->payload($rule), 'message' => 'Kural eklendi.']);
    }

    public function update(Request $request, int $id): JsonResponse
    {
        $rule = $this->findRule($id);
        $rule->fill($this->validated($request))->save();

        return response()->json(['ok' => true, 'rule' => $this->payload($rule->fresh()), 'message' => 'Kural güncellendi.']);
    }

    public function toggle(int $id): JsonResponse
    {
        $rule = $this->findRule($id);
        $rule->forceFill(['is_active' => ! $rule->is_active])->save();

        return response()->json([
            'ok' => true,
            'is_active' => (bool) $rule->is_active,
            'message' => $rule->is_active ? 'Kural etkinleştirildi.' : 'Kural durduruldu.',
        ]);
    }

    public function destroy(int $id): JsonResponse
    {
        $this->findRule($id)->delete();

        return response()->json(['ok' => true, 'message' => 'Kural silindi.']);
    }

    /**
     * Kuralı halihazırda inmiş iletilere uygular.
     *
     * Yeni yazılan bir filtrenin yalnızca bundan sonra gelene işlemesi çoğu
     * zaman istenmez; bu uç gelen kutusundaki son iletileri de tarar.
     */
    public function run(int $id): JsonResponse
    {
        @set_time_limit(300);

        $rule = $this->findRule($id);

        try {
            $affected = $this->sync->applyRuleToExisting($rule);
        } catch (\Throwable $e) {
            return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
        } finally {
            $this->sync->closeAll();
        }

        return response()->json([
            'ok' => true,
            'affected' => $affected,
            'message' => $affected > 0
                ? $affected.' ileti bu kurala göre işlendi.'
                : 'Kurala uyan ileti bulunamadı.',
        ]);
    }

    // ─────────────────────────────────────────── doğrulama

    private function validated(Request $request): array
    {
        $accountIds = $this->accountIds();

        $data = $request->validate([
            'name' => 'required|string|max:120',
            'account_id' => 'nullable|integer',
            'is_active' => 'nullable|boolean',
            'match_type' => 'required|in:all,any',
            'stop_processing' => 'nullable|boolean',
            'sort_order' => 'nullable|integer|min:0|max:999',

            'conditions' => 'required|array|min:1|max:'.self::MAX_CONDITIONS,
            'conditions.*.field' => 'required|in:from,to,subject,body',
            'conditions.*.op' => 'required|in:contains,equals,starts,ends',
            'conditions.*.value' => 'required|string|max:190',

            'actions' => 'required|array',
            'actions.move_to' => 'nullable|string|max:40',
            'actions.mark_read' => 'nullable|boolean',
            'actions.flag' => 'nullable|boolean',
            'actions.delete' => 'nullable|boolean',
        ]);

        $accountId = $request->filled('account_id') ? (int) $request->input('account_id') : null;
        if ($accountId !== null && ! in_array($accountId, $accountIds, true)) {
            abort(404, 'E-posta hesabı bulunamadı.');
        }

        $moveTo = trim((string) ($data['actions']['move_to'] ?? ''));
        $this->assertTargetAllowed($moveTo, $accountIds);

        $actions = [
            'move_to' => $moveTo,
            'mark_read' => (bool) ($data['actions']['mark_read'] ?? false),
            'flag' => (bool) ($data['actions']['flag'] ?? false),
            'delete' => (bool) ($data['actions']['delete'] ?? false),
        ];

        if ($moveTo === '' && ! $actions['mark_read'] && ! $actions['flag'] && ! $actions['delete']) {
            abort(422, 'Kuralın en az bir eylemi olmalı.');
        }

        return [
            'name' => $data['name'],
            'account_id' => $accountId,
            'is_active' => $request->boolean('is_active', true),
            'match_type' => $data['match_type'],
            'conditions_json' => array_map(fn ($c) => [
                'field' => $c['field'],
                'op' => $c['op'],
                'value' => trim($c['value']),
            ], $data['conditions']),
            'actions_json' => $actions,
            'stop_processing' => $request->boolean('stop_processing'),
            'sort_order' => (int) ($data['sort_order'] ?? 0),
        ];
    }

    /** Hedef klasör ya standart bir tip ya da kullanıcının erişebildiği bir klasör olmalı. */
    private function assertTargetAllowed(string $moveTo, array $accountIds): void
    {
        if ($moveTo === '' || in_array($moveTo, ['archive', 'junk', 'trash', 'inbox'], true)) {
            return;
        }

        $exists = is_numeric($moveTo)
            && MailFolder::whereIn('account_id', $accountIds ?: [0])->where('id', (int) $moveTo)->exists();

        abort_unless($exists, 404, 'Hedef klasör bulunamadı.');
    }

    // ─────────────────────────────────────────── engellenen göndericiler

    /*
     * Engeller kurallardan ayrı bir tabloda tutulur (bkz. mail_blocked_senders
     * migration'ı): her spam işaretlemesi bir kural yaratsaydı kurallar
     * listesi tek satırlık kayıtlarla dolar ve kullanılamaz hâle gelirdi.
     * Yine de engeller kullanıcıdan gizli olmamalı — bu uçlar, kurallar
     * penceresindeki "Engellenen Göndericiler" sekmesini besler.
     */

    /** Engel listesi + kapsam seçimi için hesaplar. */
    public function blockedIndex(): JsonResponse
    {
        return response()->json([
            'blocked' => $this->blockedList(),
            'accounts' => $this->accountsQuery()->get()->map(fn (MailAccount $a) => [
                'id' => $a->id,
                'name' => $a->name,
                'email' => $a->email,
                'color' => $a->color,
            ])->values(),
        ]);
    }

    /**
     * Gönderici engeller.
     *
     * İki yoldan çağrılır: kurallar penceresindeki elle ekleme satırı bir
     * `pattern` gönderir; posta listesindeki "Göndericiyi engelle" menüsü ise
     * seçili iletilerin kimliklerini (`ids`) gönderip adresleri sunucuda
     * çözdürür — istemcinin gönderen adresini doğru taşıdığına güvenmek yerine
     * kaynağından okumak hem güvenli hem de yazım hatalarına kapalıdır.
     */
    public function blockedStore(Request $request, MailSpamService $spam): JsonResponse
    {
        $request->validate([
            'pattern' => 'nullable|string|max:190',
            'ids' => 'nullable|array|max:200',
            'ids.*' => 'integer',
            'match_kind' => 'nullable|in:email,domain',
            'account_id' => 'nullable|integer',
            'apply_existing' => 'nullable|boolean',
        ]);

        $accountIds = $this->accountIds();

        $accountId = $request->filled('account_id') ? (int) $request->input('account_id') : null;
        if ($accountId !== null && ! in_array($accountId, $accountIds, true)) {
            abort(404, 'E-posta hesabı bulunamadı.');
        }

        $kind = $request->input('match_kind') === MailBlockedSender::KIND_DOMAIN
            ? MailBlockedSender::KIND_DOMAIN
            : MailBlockedSender::KIND_EMAIL;

        $values = $this->blockTargets($request, $accountIds);
        if ($values->isEmpty()) {
            abort(422, 'Engellenecek adres bulunamadı.');
        }

        // Tam adres beklenirken alan adı yazılmışsa (ör. "ornek.com") sessizce
        // yanlış kayıt açmak yerine kullanıcıyı uyar
        if ($kind === MailBlockedSender::KIND_EMAIL) {
            foreach ($values as $value) {
                abort_unless(filter_var($value, FILTER_VALIDATE_EMAIL), 422, 'Geçerli bir e-posta adresi girin.');
            }
        }

        $affected = 0;
        $added = 0;

        try {
            foreach ($values as $value) {
                $blocked = $spam->block($this->agencyId(), $accountId, $value, $kind, (int) Auth::id());
                if (! $blocked) {
                    continue;
                }

                if ($blocked->wasRecentlyCreated) {
                    $added++;
                }

                if ($request->boolean('apply_existing')) {
                    $affected += $spam->applyToInbox($blocked, $accountIds);
                }
            }
        } catch (\Throwable $e) {
            return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
        } finally {
            $this->sync->closeAll();
        }

        return response()->json([
            'ok' => true,
            'blocked' => $this->blockedList(),
            'affected' => $affected,
            'message' => $added === 0
                ? 'Bu gönderici zaten engelli.'
                : ($affected > 0
                    ? 'Engellendi; gelen kutusundaki '.$affected.' ileti gereksize taşındı.'
                    : 'Engellendi. Bundan sonra gelen iletiler gereksize düşecek.'),
        ]);
    }

    /** Engeli kaldırır — bu göndericiden gelenler yeniden gelen kutusuna düşer. */
    public function blockedDestroy(int $id, MailSpamService $spam): JsonResponse
    {
        $blocked = MailBlockedSender::where('agency_id', $this->agencyId())->find($id);
        abort_if(! $blocked, 404, 'Engel kaydı bulunamadı.');

        // Belirli bir hesaba bağlı engel, o hesabı göremeyen kullanıcıya kapalı
        if ($blocked->account_id && ! in_array((int) $blocked->account_id, $this->accountIds(), true)) {
            abort(404, 'Engel kaydı bulunamadı.');
        }

        $blocked->delete();
        $spam->forget($this->agencyId());

        return response()->json([
            'ok' => true,
            'blocked' => $this->blockedList(),
            'message' => 'Engel kaldırıldı.',
        ]);
    }

    /**
     * Engellenecek adresleri belirler.
     *
     * `ids` verildiyse iletilerin gönderenleri veritabanından okunur; aksi
     * hâlde elle yazılan tek desen kullanılır.
     *
     * @return \Illuminate\Support\Collection<int,string>
     */
    private function blockTargets(Request $request, array $accountIds)
    {
        $ids = collect($request->input('ids', []))->map(fn ($v) => (int) $v)->filter()->unique();

        if ($ids->isNotEmpty()) {
            return MailMessage::whereIn('id', $ids)
                ->whereIn('account_id', $accountIds ?: [0])
                ->pluck('from_email')
                ->map(fn ($v) => MailBlockedSender::normalizeEmail($v))
                ->filter()
                ->unique()
                ->values();
        }

        $pattern = MailBlockedSender::normalizeEmail($request->input('pattern'));

        return collect($pattern === '' ? [] : [$pattern]);
    }

    /** Listede gösterilecek engel kayıtları. */
    private function blockedList(): array
    {
        $accountIds = $this->accountIds();

        return MailBlockedSender::where('agency_id', $this->agencyId())
            ->where(fn ($q) => $q->whereNull('account_id')->orWhereIn('account_id', $accountIds ?: [0]))
            ->with('account:id,name,color')
            ->orderByDesc('last_hit_at')
            ->orderByDesc('id')
            ->get()
            ->map(fn (MailBlockedSender $b) => [
                'id' => $b->id,
                'pattern' => $b->pattern,
                'match_kind' => $b->match_kind,
                'account_id' => $b->account_id,
                'account_name' => $b->account?->name,
                'account_color' => $b->account?->color,
                'hits' => (int) $b->hits,
                'last_hit_at' => $b->last_hit_at?->format('d.m.Y H:i'),
                'created_at' => $b->created_at?->format('d.m.Y'),
            ])
            ->values()
            ->all();
    }
}
