<?php

namespace App\Services\Mail;

use App\Models\Mail\MailContact;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

/**
 * Panelin adres rehberi.
 *
 * Yazma penceresindeki alıcı seçimi yalnızca yazışmalardan biriken adres
 * defterine (mail_contacts) değil, panelin kendi kayıtlarına da bakar:
 * müşteriler, tedarikçiler, tedarikçi yetkilileri ve kaynaklar. Böylece
 * sistemde tanımlı herkese kartını açmadan doğrudan posta yazılabilir.
 *
 * Her kayıt birden çok adres taşıyabildiği için (customers.emails bir JSON
 * dizisi) sonuçlar ADRES başına üretilir — "Ahmet Yılmaz · iş" ile
 * "Ahmet Yılmaz · kişisel" ayrı satırlar olarak seçilebilir.
 */
class MailDirectory
{
    /**
     * Her kaynağın adres taşıyan kolonları — TEK KAYNAK.
     *
     * Hem rehber araması hem de gelen postanın göndericisini tanıma
     * (MailSenderResolver) buradan okur; "hangi tabloda hangi adres alanı var"
     * bilgisi iki yerde ayrı ayrı durursa biri güncellenip diğeri unutulur.
     *
     * JSON dizi taşıyan kolonlar (customers.emails, suppliers.emails) ile düz
     * metin kolonlar bir arada listelenir; ayrımı addressesOf() çözer.
     */
    public const ADDRESS_FIELDS = [
        'customer' => ['emails', 'email_alt', 'billing_email'],
        'supplier' => ['emails', 'billing_email'],
        'responsible' => ['email'],
        'resource' => ['email'],
        'book' => ['email'],
    ];

    /** Rehber sekmeleri — arayüzdeki filtre şeridi de bunu okur. */
    public const KINDS = [
        'customer' => ['label' => 'Müşteriler', 'single' => 'Müşteri', 'icon' => 'fa-user-tie', 'color' => '#2563eb'],
        'supplier' => ['label' => 'Tedarikçiler', 'single' => 'Tedarikçi', 'icon' => 'fa-building', 'color' => '#7c3aed'],
        'responsible' => ['label' => 'Yetkililer', 'single' => 'Yetkili', 'icon' => 'fa-id-badge', 'color' => '#0891b2'],
        'resource' => ['label' => 'Kaynaklar', 'single' => 'Kaynak', 'icon' => 'fa-bus', 'color' => '#059669'],
        'book' => ['label' => 'Adres Defteri', 'single' => 'Adres Defteri', 'icon' => 'fa-address-book', 'color' => '#64748b'],
    ];

    /**
     * Rehberde arama.
     *
     * @param  string|null  $kind  Tek bir kaynağa daralt (null = hepsi)
     * @param  int  $perKind  Kaynak başına azami sonuç
     * @return Collection<int, array{email:string,name:string,kind:string,kind_label:string,meta:string,color:string,icon:string,record_id:?int}>
     */
    public function search(int $agencyId, string $term = '', ?string $kind = null, int $perKind = 12): Collection
    {
        $term = trim($term);
        $kinds = $kind && isset(self::KINDS[$kind]) ? [$kind] : array_keys(self::KINDS);

        $out = collect();
        foreach ($kinds as $k) {
            $out = $out->concat(match ($k) {
                'customer' => $this->customers($agencyId, $term, $perKind),
                'supplier' => $this->suppliers($agencyId, $term, $perKind),
                'responsible' => $this->responsibles($agencyId, $term, $perKind),
                'resource' => $this->resources($agencyId, $term, $perKind),
                'book' => $this->book($agencyId, $term, $perKind),
                default => collect(),
            });
        }

        // Aynı adres birden çok kaynakta olabilir; ilk (öncelikli) kaynak kalır
        return $out->unique(fn ($row) => $row['kind'].'|'.$row['email'])->values();
    }

    /** Kaynak başına toplam kayıt sayıları — sekme rozetleri için. */
    public function counts(int $agencyId): array
    {
        return [
            'customer' => (int) DB::table('customers')->where('agency_id', $agencyId)->whereNull('deleted_at')->count(),
            'supplier' => (int) DB::table('suppliers')->where('agency_id', $agencyId)->whereNull('deleted_at')->count(),
            'responsible' => (int) DB::table('supplier_responsibles')
                ->join('suppliers', 'suppliers.id', '=', 'supplier_responsibles.supplier_id')
                ->where('suppliers.agency_id', $agencyId)->whereNull('suppliers.deleted_at')
                ->whereNotNull('supplier_responsibles.email')->count(),
            'resource' => (int) DB::table('resources')->where('agency_id', $agencyId)->whereNull('deleted_at')
                ->whereNotNull('email')->count(),
            'book' => (int) DB::table('mail_contacts')->where('agency_id', $agencyId)->count(),
        ];
    }

    // ─────────────────────────────────────────── adresten kayıt bulma

    /**
     * Tek bir e-posta adresinin panelde hangi kayda karşılık geldiğini bulur.
     *
     * Arama (search) "kullanıcı yazarken öneri üret" işidir; bu ise "gelen
     * postanın göndericisi kim" sorusudur ve TAM eşleşme arar. İkisi aynı alan
     * haritasını (ADDRESS_FIELDS) kullanır ama sorguları ayrıdır: burada
     * sıralama, limit ve terim yok, ilk eşleşmede durulur.
     *
     * Öncelik sırası kasıtlıdır:
     *   müşteri → yetkili → tedarikçi → kaynak
     * Müşteri ilk sırada çünkü mevcut CRM davranışının (customer_id) çakışma
     * hâlinde bozulmaması gerekir. Yetkili tedarikçiden önce gelir: aynı adres
     * hem kişiye hem kuruma yazılıysa kişi daha çok bilgi taşır.
     *
     * Adres defteri (book) KAPSAM DIŞI: mail_contacts zaten yazışmalardan
     * beslenir, yani her yazışılan adres orada bulunur — "tanındı" demek
     * anlamsızlaşırdı.
     *
     * @return array{email:string,name:string,kind:string,kind_label:string,meta:string,color:string,icon:string,record_id:?int,supplier_id:?int}|null
     */
    public function resolveEmail(int $agencyId, string $email): ?array
    {
        $email = mb_strtolower(trim($email));
        if ($email === '' || ! filter_var($email, FILTER_VALIDATE_EMAIL)) {
            return null;
        }

        foreach (['customer', 'responsible', 'supplier', 'resource'] as $kind) {
            $hit = match ($kind) {
                'customer' => $this->matchCustomer($agencyId, $email),
                'responsible' => $this->matchResponsible($agencyId, $email),
                'supplier' => $this->matchSupplier($agencyId, $email),
                'resource' => $this->matchResource($agencyId, $email),
            };

            if ($hit) {
                return $hit;
            }
        }

        return null;
    }

    private function matchCustomer(int $agencyId, string $email): ?array
    {
        $row = DB::table('customers')
            ->where('agency_id', $agencyId)
            ->whereNull('deleted_at')
            ->where($this->addressWhere('customer', $email))
            // Kişi kartı şirket kartına tercih edilir: yazışma çoğunlukla kişiyledir
            ->orderByRaw("CASE WHEN customer_type = 'corporate' THEN 1 ELSE 0 END")
            ->orderBy('id')
            ->first(['id', 'code', 'customer_type', 'first_name', 'last_name', 'company_name']);

        if (! $row) {
            return null;
        }

        $name = $row->customer_type === 'corporate'
            ? ($row->company_name ?: trim($row->first_name.' '.$row->last_name))
            : trim($row->first_name.' '.$row->last_name);

        return $this->entry(
            $email,
            $name ?: 'İsimsiz müşteri',
            'customer',
            ($row->customer_type === 'corporate' ? 'Şirket' : 'Kişi').' · #'.$row->code,
            (int) $row->id,
        );
    }

    private function matchSupplier(int $agencyId, string $email): ?array
    {
        $row = DB::table('suppliers')
            ->where('agency_id', $agencyId)
            ->whereNull('deleted_at')
            ->where($this->addressWhere('supplier', $email))
            ->orderBy('id')
            ->first(['id', 'code', 'supplier_type', 'first_name', 'last_name', 'company_name']);

        if (! $row) {
            return null;
        }

        $types = \App\Models\Supplier\Supplier::supplierTypes();

        return $this->entry(
            $email,
            $row->company_name ?: trim($row->first_name.' '.$row->last_name) ?: 'İsimsiz tedarikçi',
            'supplier',
            ($types[$row->supplier_type] ?? 'Tedarikçi').' · #'.$row->code,
            (int) $row->id,
        );
    }

    /** Yetkili eşleşmesi bağlı olduğu tedarikçiyi de taşır — bağ kuruma kurulur. */
    private function matchResponsible(int $agencyId, string $email): ?array
    {
        $row = DB::table('supplier_responsibles')
            ->join('suppliers', 'suppliers.id', '=', 'supplier_responsibles.supplier_id')
            ->where('suppliers.agency_id', $agencyId)
            ->whereNull('suppliers.deleted_at')
            ->whereRaw('LOWER(supplier_responsibles.email) = ?', [$email])
            ->orderBy('supplier_responsibles.id')
            ->first([
                'supplier_responsibles.id',
                'supplier_responsibles.name',
                'supplier_responsibles.position',
                'suppliers.id as supplier_id',
                'suppliers.company_name',
            ]);

        if (! $row) {
            return null;
        }

        $entry = $this->entry(
            $email,
            $row->name ?: $email,
            'responsible',
            trim(($row->company_name ?: 'Tedarikçi').($row->position ? ' · '.$row->position : '')),
            (int) $row->id,
        );

        if ($entry) {
            $entry['supplier_id'] = (int) $row->supplier_id;
        }

        return $entry;
    }

    private function matchResource(int $agencyId, string $email): ?array
    {
        $row = DB::table('resources')
            ->where('agency_id', $agencyId)
            ->whereNull('deleted_at')
            ->whereRaw('LOWER(email) = ?', [$email])
            ->orderBy('id')
            ->first(['id', 'code', 'name', 'type', 'is_active']);

        if (! $row) {
            return null;
        }

        $types = ['guide' => 'Rehber', 'vehicle' => 'Araç', 'captain' => 'Kaptan'];

        return $this->entry(
            $email,
            $row->name ?: $email,
            'resource',
            ($types[$row->type] ?? 'Kaynak').' · #'.$row->code.($row->is_active ? '' : ' · pasif'),
            (int) $row->id,
        );
    }

    /**
     * Kaynağın adres kolonlarında tam eşleşme koşulu.
     *
     * JSON dizi taşıyan kolonlarda (customers.emails) adres alıntılı biçimde
     * aranır — "a@b.com" bir başkasının "xa@b.com" adresiyle eşleşmesin.
     * Emsal: MailCustomerLinker::queryByEmail().
     */
    private function addressWhere(string $kind, string $email): \Closure
    {
        $needle = '%"'.str_replace(['%', '_'], ['\%', '\_'], $email).'"%';

        return function ($q) use ($kind, $email, $needle) {
            foreach (self::ADDRESS_FIELDS[$kind] as $field) {
                // emails kolonları JSON metni, diğerleri düz adres
                if ($field === 'emails') {
                    $q->orWhere($field, 'like', $needle);
                } else {
                    $q->orWhereRaw('LOWER('.$field.') = ?', [$email]);
                }
            }
        };
    }

    // ─────────────────────────────────────────── kaynaklar

    private function customers(int $agencyId, string $term, int $limit): Collection
    {
        $rows = DB::table('customers')
            ->where('agency_id', $agencyId)
            ->whereNull('deleted_at')
            ->when($term !== '', fn ($q) => $q->where(function ($w) use ($term) {
                $like = $this->like($term);
                $w->where('first_name', 'like', $like)
                    ->orWhere('last_name', 'like', $like)
                    ->orWhere('company_name', 'like', $like)
                    ->orWhere('emails', 'like', $like)
                    ->orWhere('billing_email', 'like', $like)
                    ->orWhereRaw("CONCAT(COALESCE(first_name,''),' ',COALESCE(last_name,'')) like ?", [$like]);
            }))
            ->orderByRaw("CASE WHEN customer_type = 'corporate' THEN company_name ELSE first_name END")
            ->limit($limit * 2)
            ->get(['id', 'code', 'customer_type', 'first_name', 'last_name', 'company_name', 'emails', 'email_alt', 'billing_email']);

        return $this->expand($rows, 'customer', $limit, function ($row) {
            $name = $row->customer_type === 'corporate'
                ? ($row->company_name ?: trim($row->first_name.' '.$row->last_name))
                : trim($row->first_name.' '.$row->last_name);

            return [
                'name' => $name ?: 'İsimsiz müşteri',
                'meta' => ($row->customer_type === 'corporate' ? 'Şirket' : 'Kişi').' · #'.$row->code,
            ];
        }, self::ADDRESS_FIELDS['customer']);
    }

    private function suppliers(int $agencyId, string $term, int $limit): Collection
    {
        $rows = DB::table('suppliers')
            ->where('agency_id', $agencyId)
            ->whereNull('deleted_at')
            ->when($term !== '', fn ($q) => $q->where(function ($w) use ($term) {
                $like = $this->like($term);
                $w->where('company_name', 'like', $like)
                    ->orWhere('first_name', 'like', $like)
                    ->orWhere('last_name', 'like', $like)
                    ->orWhere('emails', 'like', $like)
                    ->orWhere('billing_email', 'like', $like);
            }))
            ->orderBy('company_name')
            ->limit($limit * 2)
            ->get(['id', 'code', 'supplier_type', 'first_name', 'last_name', 'company_name', 'emails', 'billing_email']);

        $types = \App\Models\Supplier\Supplier::supplierTypes();

        return $this->expand($rows, 'supplier', $limit, fn ($row) => [
            'name' => $row->company_name ?: trim($row->first_name.' '.$row->last_name) ?: 'İsimsiz tedarikçi',
            'meta' => ($types[$row->supplier_type] ?? 'Tedarikçi').' · #'.$row->code,
        ], self::ADDRESS_FIELDS['supplier']);
    }

    /** Tedarikçi yetkilileri — kurumun genel adresi yerine kişiye yazmak için. */
    private function responsibles(int $agencyId, string $term, int $limit): Collection
    {
        $rows = DB::table('supplier_responsibles')
            ->join('suppliers', 'suppliers.id', '=', 'supplier_responsibles.supplier_id')
            ->where('suppliers.agency_id', $agencyId)
            ->whereNull('suppliers.deleted_at')
            ->whereNotNull('supplier_responsibles.email')
            ->where('supplier_responsibles.email', '!=', '')
            ->when($term !== '', fn ($q) => $q->where(function ($w) use ($term) {
                $like = $this->like($term);
                $w->where('supplier_responsibles.name', 'like', $like)
                    ->orWhere('supplier_responsibles.email', 'like', $like)
                    ->orWhere('suppliers.company_name', 'like', $like);
            }))
            ->orderBy('supplier_responsibles.name')
            ->limit($limit)
            ->get([
                'supplier_responsibles.id',
                'supplier_responsibles.name',
                'supplier_responsibles.email',
                'supplier_responsibles.position',
                'suppliers.company_name',
            ]);

        return $rows->map(fn ($row) => $this->entry(
            $row->email,
            $row->name ?: $row->email,
            'responsible',
            trim(($row->company_name ?: 'Tedarikçi').($row->position ? ' · '.$row->position : '')),
            (int) $row->id,
        ))->filter()->values();
    }

    private function resources(int $agencyId, string $term, int $limit): Collection
    {
        $rows = DB::table('resources')
            ->where('agency_id', $agencyId)
            ->whereNull('deleted_at')
            ->whereNotNull('email')
            ->where('email', '!=', '')
            ->when($term !== '', fn ($q) => $q->where(function ($w) use ($term) {
                $like = $this->like($term);
                $w->where('name', 'like', $like)->orWhere('email', 'like', $like);
            }))
            ->orderBy('name')
            ->limit($limit)
            ->get(['id', 'code', 'name', 'email', 'type', 'is_active']);

        $types = ['guide' => 'Rehber', 'vehicle' => 'Araç', 'captain' => 'Kaptan'];

        return $rows->map(fn ($row) => $this->entry(
            $row->email,
            $row->name ?: $row->email,
            'resource',
            ($types[$row->type] ?? 'Kaynak').' · #'.$row->code.($row->is_active ? '' : ' · pasif'),
            (int) $row->id,
        ))->filter()->values();
    }

    /** Yazışmalardan biriken adresler. */
    private function book(int $agencyId, string $term, int $limit): Collection
    {
        $rows = MailContact::where('agency_id', $agencyId)
            ->when($term !== '', fn ($q) => $q->where(function ($w) use ($term) {
                $like = $this->like($term);
                $w->where('email', 'like', $like)->orWhere('name', 'like', $like);
            }))
            ->orderByDesc('usage_count')
            ->orderByDesc('last_used_at')
            ->limit($limit)
            ->get(['id', 'email', 'name', 'usage_count']);

        return $rows->map(fn ($row) => $this->entry(
            $row->email,
            $row->name ?: $row->email,
            'book',
            $row->usage_count > 1 ? $row->usage_count.' yazışma' : 'Yazışmadan',
            (int) $row->id,
        ))->filter()->values();
    }

    // ─────────────────────────────────────────── yardımcılar

    /**
     * Bir kaydın birden çok adres alanını ayrı satırlara açar.
     *
     * @param  callable  $describe  Kayıttan ['name','meta'] üretir
     * @param  string[]  $fields    Adres taşıyan kolonlar (JSON dizi ya da düz metin)
     */
    private function expand($rows, string $kind, int $limit, callable $describe, array $fields): Collection
    {
        $out = collect();

        foreach ($rows as $row) {
            $info = $describe($row);
            $seen = [];

            foreach ($fields as $field) {
                foreach ($this->addressesOf($row->{$field} ?? null) as $email) {
                    if (isset($seen[$email])) {
                        continue;
                    }
                    $seen[$email] = true;

                    $entry = $this->entry($email, $info['name'], $kind, $info['meta'], (int) $row->id);
                    if ($entry) {
                        $out->push($entry);
                    }
                }
            }

            if ($out->count() >= $limit) {
                break;
            }
        }

        return $out->take($limit);
    }

    /** JSON dizi ya da düz metin olabilen adres alanını listeye çevirir. */
    private function addressesOf($value): array
    {
        if (blank($value)) {
            return [];
        }

        $list = is_array($value) ? $value : (json_decode((string) $value, true) ?: [(string) $value]);

        return collect($list)
            ->map(fn ($e) => mb_strtolower(trim((string) $e)))
            ->filter(fn ($e) => $e !== '' && filter_var($e, FILTER_VALIDATE_EMAIL))
            ->unique()
            ->values()
            ->all();
    }

    private function entry(?string $email, string $name, string $kind, string $meta, ?int $recordId): ?array
    {
        $email = mb_strtolower(trim((string) $email));
        if ($email === '' || ! filter_var($email, FILTER_VALIDATE_EMAIL)) {
            return null;
        }

        $spec = self::KINDS[$kind];

        return [
            'email' => $email,
            'name' => trim($name) ?: $email,
            'kind' => $kind,
            'kind_label' => $spec['single'],
            'icon' => $spec['icon'],
            'color' => $spec['color'],
            'meta' => $meta,
            'record_id' => $recordId,
        ];
    }

    private function like(string $term): string
    {
        return '%'.str_replace(['%', '_'], ['\%', '\_'], $term).'%';
    }
}
