<?php

namespace App\Services;

use App\Mail\AppointmentConfirmedMail;
use App\Mail\PaymentReceivedMail;
use App\Mail\ServiceReminderMail;
use App\Mail\WorkOrderReadyMail;
use App\Models\Central\Company;
use App\Models\Tenant\Appointment;
use App\Models\Tenant\Client;
use App\Models\Tenant\Payment;
use App\Models\Tenant\Vehicle;
use App\Models\Tenant\WorkOrder;
use App\Services\Notifications\TelegramService;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;

/**
 * Multi-channel outbound notifications with per-tenant + per-client opt-in/out.
 *
 * Channels tried in order:
 *   1. Telegram — if client has telegram_chat_id AND tenant bot token AND
 *      tenant has notify.{type}.telegram = true (default true when bot configured)
 *   2. Email — if client has email AND tenant has notify.{type}.email = true
 *      (default true — preserves legacy behaviour)
 *
 * Per-client overrides via clients.notify_prefs JSON (array of channel keys).
 *
 * Public methods return TRUE when at least one channel succeeded.
 */
class NotificationDispatcher
{
    public function __construct(private TelegramService $telegram)
    {
    }

    public function workOrderReady(WorkOrder $wo): bool
    {
        $company = $this->companyFor($wo);
        $client = $wo->client;
        if (! $client) return false;

        return $this->dispatch($company, $client, 'wo_ready', [
            'telegram' => fn () => $this->tgWorkOrderReady($wo, $company, $client),
            'email' => fn () => $this->emailSafe(
                fn () => Mail::to($client->email)->send(new WorkOrderReadyMail($wo, $company)),
                'workOrderReady', ['wo' => $wo->id]
            ),
        ], workOrderId: $wo->id);
    }

    public function paymentReceived(Payment $payment): bool
    {
        $company = $this->companyFor($payment);
        $client = $payment->client;
        if (! $client) return false;

        return $this->dispatch($company, $client, 'payment', [
            'telegram' => fn () => $this->tgPaymentReceived($payment, $company, $client),
            'email' => fn () => $this->emailSafe(
                fn () => Mail::to($client->email)->send(new PaymentReceivedMail($payment, $company)),
                'paymentReceived', ['payment' => $payment->id]
            ),
        ], workOrderId: $payment->work_order_id);
    }

    public function appointmentConfirmed(Appointment $a): bool
    {
        $company = $this->companyFor($a);
        $client = $a->client;
        if (! $client) return false;

        return $this->dispatch($company, $client, 'appointment', [
            'telegram' => fn () => $this->tgAppointmentConfirmed($a, $company, $client),
            'email' => fn () => $this->emailSafe(
                fn () => Mail::to($client->email)->send(new AppointmentConfirmedMail($a, $company)),
                'appointmentConfirmed', ['appt' => $a->id]
            ),
        ]);
    }

    public function serviceReminder(Vehicle $v, string $type = 'general', ?string $note = null): bool
    {
        $company = $this->companyFor($v);
        $client = $v->client;
        if (! $client) return false;

        return $this->dispatch($company, $client, 'reminder', [
            'telegram' => fn () => $this->tgServiceReminder($v, $type, $note, $company, $client),
            'email' => fn () => $this->emailSafe(
                fn () => Mail::to($client->email)->send(new ServiceReminderMail($v, $type, $note, $company)),
                'serviceReminder', ['vehicle' => $v->id]
            ),
        ]);
    }

    public function tireSeasonalSwap(\App\Models\Tenant\TireSet $set): bool
    {
        $company = $this->companyFor($set);
        $client = $set->client;
        if (! $client) return false;

        return $this->dispatch($company, $client, 'reminder', [
            'telegram' => fn () => $this->tgTireSeasonalSwap($set, $company, $client),
            'email' => fn () => $set->vehicle ? $this->emailSafe(
                fn () => Mail::to($client->email)->send(new ServiceReminderMail(
                    $set->vehicle,
                    'tire_swap',
                    'E timpul să schimbi anvelopele ' . ($set->season === 'winter' ? 'de iarnă' : 'de vară') .
                        ' (' . $set->sizeLabel() . ').',
                    $company
                )),
                'tireSeasonalSwap', ['set' => $set->id]
            ) : false,
        ]);
    }

    protected function tgTireSeasonalSwap(\App\Models\Tenant\TireSet $set, Company $company, Client $client): bool
    {
        $brand = htmlspecialchars($company->display_name ?? $company->name);
        $size = htmlspecialchars($set->sizeLabel());
        $seasonRo = $set->season === 'winter' ? 'de iarnă' : 'de vară';
        $loc = $set->currentStorage()?->location;
        $plate = $set->vehicle?->plate ? ' · ' . htmlspecialchars($set->vehicle->plate) : '';

        $text = "🔧 <b>Schimb sezonier anvelope</b>\n"
            . "Setul tău {$seasonRo} ({$size}){$plate}"
            . ($loc ? " e în depozit la <b>{$loc}</b>." : '.')
            . "\n\nProgramează-te la <b>{$brand}</b>.";

        return $this->telegram->sendMessage($company, (string) $client->telegram_chat_id, $text);
    }

    // ─── Channel dispatch ─────────────────────────────────────────

    /**
     * @param  array<string, callable(): bool>  $senders  channel-key → sender callback
     * @return bool Returns the channel name that delivered, or null on full miss.
     */
    protected function dispatch(Company $company, Client $client, string $key, array $senders, ?int $workOrderId = null): bool
    {
        $any = false;
        foreach ($this->channelsFor($company, $client, $key) as $channel) {
            if (! isset($senders[$channel])) continue;
            try {
                $ok = ($senders[$channel])() === true;
                $this->logNotification($company->id, $workOrderId, $client->id, $channel, $key, $ok);
                if ($ok) {
                    $any = true;
                    break;
                }
            } catch (\Throwable $e) {
                Log::warning("notify.{$key} {$channel} threw", ['err' => $e->getMessage()]);
                $this->logNotification($company->id, $workOrderId, $client->id, $channel, $key, false, $e->getMessage());
            }
        }
        return $any;
    }

    /** Append-only log entry — never throw from here, swallow DB errors. */
    protected function logNotification(int $companyId, ?int $workOrderId, ?int $clientId, string $channel, string $key, bool $success, ?string $error = null): void
    {
        try {
            \App\Models\Tenant\ClientNotificationLog::create([
                'company_id' => $companyId,
                'work_order_id' => $workOrderId,
                'client_id' => $clientId,
                'channel' => $channel,
                'template_key' => $key,
                'status' => $success ? 'sent' : 'failed',
                'error_detail' => $error,
                'sent_at' => now(),
            ]);
        } catch (\Throwable $e) { /* never break sending because of logging */ }
    }

    /**
     * Resolve which channels to try and in what order, applying per-client
     * preference if set, otherwise the tenant default.
     */
    protected function channelsFor(Company $company, Client $client, string $key): array
    {
        $prefs = (array) ($client->notify_prefs ?? []);

        // Per-client list takes precedence.
        if (! empty($prefs)) {
            $candidates = array_values(array_intersect(['telegram', 'email'], $prefs));
        } else {
            $candidates = ['telegram', 'email'];
        }

        return array_values(array_filter(
            $candidates,
            fn (string $ch) => $this->channelEnabled($company, $client, $key, $ch)
        ));
    }

    protected function channelEnabled(Company $company, Client $client, string $key, string $channel): bool
    {
        $notify = (array) data_get($company->settings, 'notify', []);
        // Tenant-level: notify.{type}.{channel} — accept legacy boolean notify.{type}=true as email-only flag.
        $tenantValue = $notify[$key] ?? null;
        if (is_bool($tenantValue)) {
            // legacy: email default true, telegram default true if bot configured
            if ($channel === 'email') $tenantValueEnabled = $tenantValue;
            elseif ($channel === 'telegram') $tenantValueEnabled = $tenantValue && (bool) $this->telegram->tokenFor($company);
            else $tenantValueEnabled = false;
        } elseif (is_array($tenantValue)) {
            $tenantValueEnabled = (bool) ($tenantValue[$channel] ?? true);
        } else {
            $tenantValueEnabled = true;
        }

        if (! $tenantValueEnabled) return false;

        // Per-client field presence
        if ($channel === 'telegram') {
            return ! empty($client->telegram_chat_id) && (bool) $this->telegram->tokenFor($company);
        }
        if ($channel === 'email') {
            return ! empty($client->email);
        }
        return false;
    }

    // ─── Telegram message builders ────────────────────────────────

    protected function tgWorkOrderReady(WorkOrder $wo, Company $company, Client $client): bool
    {
        $brand = htmlspecialchars($company->display_name ?? $company->name);
        $no = htmlspecialchars((string) $wo->number);
        $plate = htmlspecialchars((string) ($wo->vehicle->plate ?? ''));
        $text = "✅ <b>Mașina e gata de ridicat</b>\n"
            . "Fișa #{$no} · {$plate}\n"
            . "Total: <b>" . number_format((float) $wo->total, 2) . " " . ($company->settings['currency'] ?? 'MDL') . "</b>\n\n"
            . "🔗 Detalii: " . $wo->trackingUrl() . "\n\n"
            . "{$brand}";
        return $this->telegram->sendMessage($company, (string) $client->telegram_chat_id, $text);
    }

    protected function tgPaymentReceived(Payment $payment, Company $company, Client $client): bool
    {
        $amt = number_format((float) $payment->amount, 2);
        $cur = $company->settings['currency'] ?? 'MDL';
        $text = "💳 <b>Plată primită</b>\n"
            . "Suma: <b>{$amt} {$cur}</b>\n"
            . "Mulțumim!";
        return $this->telegram->sendMessage($company, (string) $client->telegram_chat_id, $text);
    }

    protected function tgAppointmentConfirmed(Appointment $a, Company $company, Client $client): bool
    {
        $when = $a->starts_at?->isoFormat('D MMM YYYY, HH:mm') ?? '?';
        $text = "📅 <b>Programare confirmată</b>\n"
            . "Data: <b>{$when}</b>\n"
            . ($a->vehicle?->plate ? "Auto: " . htmlspecialchars($a->vehicle->plate) . "\n" : '')
            . "Te așteptăm.";
        return $this->telegram->sendMessage($company, (string) $client->telegram_chat_id, $text);
    }

    protected function tgServiceReminder(Vehicle $v, string $type, ?string $note, Company $company, Client $client): bool
    {
        $brand = htmlspecialchars($company->display_name ?? $company->name);
        $plate = htmlspecialchars((string) ($v->plate ?? ''));
        $text = "🔧 <b>Reminder service</b>\n"
            . "{$brand}: " . htmlspecialchars($v->make . ' ' . $v->model) . " · {$plate}\n"
            . ($note ?: 'A trecut ceva timp de la ultima vizită — recomandăm o verificare.');
        return $this->telegram->sendMessage($company, (string) $client->telegram_chat_id, $text);
    }

    // ─── Helpers ──────────────────────────────────────────────────

    protected function emailSafe(callable $fn, string $tag, array $ctx = []): bool
    {
        try {
            $fn();
            return true;
        } catch (\Throwable $e) {
            Log::warning("{$tag} mail failed", $ctx + ['err' => $e->getMessage()]);
            return false;
        }
    }

    protected function companyFor($model): Company
    {
        return Company::withoutGlobalScopes()->findOrFail($model->company_id);
    }
}
