HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/be.naniguide.com/app/Http/Controllers/Refactor/SendingController.php
<?php

namespace App\Http\Controllers\Refactor;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Model\SendingServer;
use App\Model\SendingIdentity;
use App\Model\Sender;
use App\SendingServers\Identities\IdentityCreationService;
use App\SendingServers\Identities\IdentityKind;
use App\SendingServers\Identities\IdentityStatus;
use App\SendingServers\Identities\ManagementMode;
use App\SendingServers\DomainVerification\RecordPurpose;
use App\Model\TrackingDomain;
use App\Model\EmailVerificationServer;
use App\Model\Blacklist;
use App\Model\Signature;
use App\Model\Setting;
use App\Model\BounceHandler;
use App\Model\FeedbackLoopHandler;
use App\Library\Facades\Hook;
use App\Services\Customer\SendingDomainService;
use App\Services\Customer\SendingServerService;
use App\Services\Customer\VerificationServerService;
use App\Services\Plans\Entitlements\EntitlementKey;
use App\Services\Plans\Gates\EntitlementGate;
use App\Services\Plans\Gates\QuotaGate;
use App\Services\Plans\Quotas\QuotaKey;
use App\Services\Plans\Quotas\QuotasService;
use Gate;

class SendingController extends Controller
{
    // ─── Sending Servers ────────────────────────────────────────

    public function servers(Request $request, SendingServerService $service)
    {
        $customer = $request->user()->customer;
        if (!EntitlementGate::allows($customer, EntitlementKey::HAS_OWN_SENDING_SERVER)) {
            return $this->notAuthorized();
        }
        if (!$customer->can('read', new SendingServer())) {
            return $this->notAuthorized();
        }

        return view('refactor.pages.sending.servers', [
            'customer' => $customer,
            'meta'     => $service->headerMeta($customer),
            'sidebar'  => $service->sidebarStats($customer),
        ]);
    }

    public function serversListing(Request $request, SendingServerService $service)
    {
        $customer = $request->user()->customer;
        if (!EntitlementGate::allows($customer, EntitlementKey::HAS_OWN_SENDING_SERVER)) {
            return $this->notAuthorized();
        }
        if (!$customer->can('read', new SendingServer())) {
            return $this->notAuthorized();
        }

        $servers = $service->list(
            $customer,
            $request->input('keyword') ?: null,
            $request->input('status') ?: null,
            $request->input('type') ?: null,
            (string) ($request->input('sort_order') ?: 'created_at'),
            (string) ($request->input('sort_direction') ?: 'desc'),
            (int) ($request->input('per_page') ?: 25),
        );

        return view('refactor.pages.sending._servers_list', [
            'servers' => $servers,
        ]);
    }

    // ─── Sending Domains ────────────────────────────────────────

    public function domains(Request $request, SendingDomainService $service)
    {
        $customer = $request->user()->customer;
        if (!$service->customerCanVerify($customer)) {
            return $this->notAuthorized();
        }

        return view('refactor.pages.sending.domains', [
            'customer' => $customer,
            'stats'    => $service->dashboardStats($customer),
            'sidebar'  => $service->sidebarStats($customer),
        ]);
    }

    public function domainsListing(Request $request, SendingDomainService $service)
    {
        $customer = $request->user()->customer;
        if (!$service->customerCanVerify($customer)) {
            return $this->notAuthorized();
        }

        $domains = $service->list(
            $customer,
            $request->input('keyword') ?: null,
            $request->input('status') ?: null,
            (string) ($request->input('sort_order') ?: 'created_at'),
            (string) ($request->input('sort_direction') ?: 'desc'),
            (int) ($request->input('per_page') ?: 25),
        );

        // Plan's primary system server — drives the per-row "usable?" badge.
        // null when plan uses customer's own server or there's no subscription.
        $subscription = $customer->getCurrentActiveSubscription();
        $plan = $subscription?->plan;
        $primaryServer = ($plan && $plan->useSystemSendingServer())
            ? $plan->primarySendingServer()
            : null;

        return view('refactor.pages.sending._domains_list', [
            'domains'       => $domains,
            'primaryServer' => $primaryServer,
        ]);
    }

    // ─── Verified Senders ───────────────────────────────────────

    public function senders(Request $request)
    {
        return redirect()->route('refactor.sending.domains');
        $customer = $request->user()->customer;
        $plan = $customer->getCurrentActiveSubscription()?->plan;

        if (!$plan || !$plan->allowSenderVerification()) {
            return $this->notAuthorized();
        }

        $total = Sender::where('customer_id', $customer->id)->count();
        $verified = Sender::where('customer_id', $customer->id)->where('status', Sender::STATUS_VERIFIED)->count();

        return view('refactor.pages.sending.senders', [
            'customer' => $customer,
            'total' => $total,
            'verified' => $verified,
        ]);
    }

    public function sendersListing(Request $request)
    {
        $customer = $request->user()->customer;
        $request->merge(['customer_id' => $customer->id]);
        $senders = Sender::search($request)->paginate($request->per_page ?? 25);

        return view('refactor.pages.sending._senders_list', [
            'senders' => $senders,
        ]);
    }

    // ─── Tracking Domains ───────────────────────────────────────

    /**
     * Feature gate for the Tracking Domains screens. MAX_TRACKING_DOMAINS quota
     * acts as both the access switch and the resource cap:
     *   - limit === 0      → feature disabled on plan (also: no subscription)
     *   - limit === null   → unlimited
     *   - limit > 0        → access granted, cap enforced at create time
     */
    private function trackingDomainsAccessible(\App\Model\Customer $customer): bool
    {
        $limit = app(QuotasService::class)->limit($customer, QuotaKey::MAX_TRACKING_DOMAINS);
        return $limit === null || $limit > 0;
    }

    public function trackingDomains(Request $request)
    {
        $customer = $request->user()->customer;
        if (!$this->trackingDomainsAccessible($customer)) {
            return $this->notAuthorized();
        }

        $total = $customer->local()->trackingDomains()->count();
        $verified = $customer->local()->trackingDomains()->where('status', TrackingDomain::STATUS_VERIFIED)->count();

        return view('refactor.pages.sending.tracking_domains', [
            'customer' => $customer,
            'total' => $total,
            'verified' => $verified,
        ]);
    }

    public function trackingDomainsListing(Request $request)
    {
        $customer = $request->user()->customer;
        if (!$this->trackingDomainsAccessible($customer)) {
            return $this->notAuthorized();
        }

        $request->merge(['customer_id' => $customer->id]);
        $domains = TrackingDomain::search($request)->paginate($request->per_page ?? 25);

        return view('refactor.pages.sending._tracking_domains_list', [
            'domains' => $domains,
        ]);
    }

    public function trackingDomainCreate(Request $request)
    {
        $customer = $request->user()->customer;
        if (!QuotaGate::allows($customer, QuotaKey::MAX_TRACKING_DOMAINS, amount: 1)) {
            return $this->notAuthorized();
        }
        $domain = new TrackingDomain(['scheme' => 'https']);
        $domain->status = TrackingDomain::STATUS_UNVERIFIED;
        $domain->uid = '0';
        return view('refactor.pages.sending.tracking_domain_form', [
            'domain'    => $domain,
            'isCreate'  => true,
        ]);
    }

    public function trackingDomainStore(Request $request)
    {
        $customer = $request->user()->customer;
        if (!QuotaGate::allows($customer, QuotaKey::MAX_TRACKING_DOMAINS, amount: 1)) {
            return $this->notAuthorized();
        }

        [$domain, $validator] = TrackingDomain::createFromRequest($request);

        if ($validator->fails()) {
            return back()->withErrors($validator)->withInput();
        }

        return redirect()->route('refactor.sending.tracking_domain_verify', $domain->uid)
            ->with('alert-success', trans('refactor/sending.tracking.flash_created'));
    }

    public function trackingDomainEdit(Request $request, $uid)
    {
        $customer = $request->user()->customer;
        if (!$this->trackingDomainsAccessible($customer)) {
            return $this->notAuthorized();
        }
        $domain = TrackingDomain::findByUid($uid);
        return view('refactor.pages.sending.tracking_domain_form', [
            'domain'       => $domain,
            'isCreate'     => false,
        ]);
    }

    public function trackingDomainUpdate(Request $request, $uid)
    {
        $customer = $request->user()->customer;
        if (!$this->trackingDomainsAccessible($customer)) {
            return $this->notAuthorized();
        }
        $domain = TrackingDomain::findByUid($uid);

        $validator = \Illuminate\Support\Facades\Validator::make($request->all(), [
            'port' => 'nullable|integer|between:1,65535',
        ]);
        if ($validator->fails()) {
            return back()->withErrors($validator)->withInput();
        }

        [$name, $port] = TrackingDomain::parseNameAndPortFromRequest($request);

        $domain->scheme = $request->input('scheme', 'http');
        $domain->name   = $name;
        $domain->port   = $port;
        $domain->verification_method = $request->input('verification_method', TrackingDomain::VERIFICATION_METHOD_HOST);
        $domain->status = TrackingDomain::STATUS_UNVERIFIED;
        $domain->save();

        return redirect()->route('refactor.sending.tracking_domain_verify', $domain->uid)
            ->with('alert-success', trans('refactor/sending.tracking.flash_updated'));
    }

    public function trackingDomainVerify(Request $request, $uid)
    {
        $customer = $request->user()->customer;
        if (!$this->trackingDomainsAccessible($customer)) {
            return $this->notAuthorized();
        }
        $domain = TrackingDomain::findByUid($uid);
        $hostname = parse_url(url('/'), PHP_URL_HOST);

        return view('refactor.pages.sending.tracking_domain_verify', [
            'domain'               => $domain,
            'hostname'             => $hostname,
            'caddyServerHostname'  => config('caddy.server_hostname', $hostname),
            'isAutossl'            => config('caddy.autossl') == true,
            'downloadUrl'          => url('tracking-1.0.0.zip'),
        ]);
    }

    public function trackingDomainCheck(Request $request, $uid)
    {
        $customer = $request->user()->customer;
        if (!$this->trackingDomainsAccessible($customer)) {
            return response()->json(['success' => false], 403);
        }
        $domain = TrackingDomain::findByUid($uid);
        $domain->verify();
        return response()->json([
            'success' => $domain->isVerified(),
            'status'  => $domain->status,
            'message' => $domain->isVerified()
                ? trans('refactor/sending.tracking.flash_verified')
                : trans('refactor/sending.tracking.verify_failed'),
        ]);
    }

    // ─── Email Verification Servers ─────────────────────────────

    public function verificationServers(Request $request, VerificationServerService $service)
    {
        $customer = $request->user()->customer;

        if (!EntitlementGate::allows($customer, EntitlementKey::HAS_OWN_EMAIL_VERIFICATION_SERVER)) {
            return $this->notAuthorized();
        }

        return view('refactor.pages.sending.verification_servers', [
            'customer' => $customer,
            'meta'     => $service->headerMeta($customer),
            'sidebar'  => $service->sidebarStats($customer),
        ]);
    }

    public function verificationServersListing(Request $request, VerificationServerService $service)
    {
        $customer = $request->user()->customer;

        $servers = $service->list(
            $customer,
            $request->input('keyword') ?: null,
            $request->input('status') ?: null,
            (string) ($request->input('sort_order') ?: 'created_at'),
            (string) ($request->input('sort_direction') ?: 'desc'),
            (int) ($request->input('per_page') ?: 25),
        );

        return view('refactor.pages.sending._verification_servers_list', [
            'servers' => $servers,
        ]);
    }

    // ─── Blacklist ──────────────────────────────────────────────

    public function blacklist(Request $request)
    {
        $customer = $request->user()->customer;
        $total = $customer->blacklists()->count();

        return view('refactor.pages.sending.blacklist', [
            'customer' => $customer,
            'total' => $total,
        ]);
    }

    public function blacklistListing(Request $request)
    {
        $customer = $request->user()->customer;
        $request->merge(['customer_id' => $customer->id]);
        $blacklists = Blacklist::search($request)->paginate($request->per_page ?? 25);

        return view('refactor.pages.sending._blacklist_list', [
            'blacklists' => $blacklists,
        ]);
    }

    public function blacklistDelete(Request $request)
    {
        $customer = $request->user()->customer;

        if ($request->select_tool == 'all_items') {
            $blacklists = $customer->blacklists();
        } else {
            $blacklists = Blacklist::whereIn(
                'id',
                is_array($request->uids) ? $request->uids : explode(',', $request->uids)
            );
        }

        $deleted = 0;
        foreach ($blacklists->get() as $blacklist) {
            if ($customer->can('delete', $blacklist)) {
                $blacklist->delist($customer);
                $blacklist->delete();
                $deleted++;
            }
        }

        return response()->json([
            'message' => $deleted . ' ' . trans('refactor/sending.blacklist.flash_deleted'),
        ]);
    }

    public function blacklistStore(Request $request)
    {
        $customer = $request->user()->customer;
        if (!$customer->can('create', new Blacklist())) {
            return response()->json(['status' => 'error', 'message' => 'Not authorized'], 403);
        }

        $validator = \Validator::make($request->all(), [
            'email' => 'required|email',
        ]);

        if ($validator->fails()) {
            return response()->json(['status' => 'error', 'errors' => $validator->errors()], 422);
        }

        $blacklist = new Blacklist();
        $blacklist->fill($request->only(['email', 'reason']));
        $blacklist->customer_id = $customer->id;
        $blacklist->save();
        // Activity log not tracked for blacklist entries

        return response()->json([
            'status' => 'success',
            'message' => trans('refactor/sending.blacklist.flash_added'),
        ]);
    }

    public function blacklistImport(Request $request)
    {
        $customer = $request->user()->customer;
        $job = $customer->importBlacklistJobs()->first();

        return view('refactor.pages.sending.blacklist_import', [
            'customer' => $customer,
            'job' => $job,
        ]);
    }

    public function blacklistStartImport(Request $request)
    {
        $customer = $request->user()->customer;

        if (!$request->hasFile('file')) {
            return response()->json(['status' => 'error', 'message' => 'No file uploaded'], 422);
        }

        $filepath = Blacklist::upload($request->file('file'));
        $job = $customer->dispatchWithMonitor(new \App\Jobs\ImportBlacklistJob($filepath, $customer));

        return response()->json([
            'status' => 'success',
            'message' => trans('refactor/sending.blacklist.import_started'),
            'job_uid' => $job->uid,
        ]);
    }

    public function blacklistImportProgress(Request $request, $job_uid)
    {
        $job = \App\Model\JobMonitor::findByUid($job_uid);
        if (!$job) {
            return response()->json(['status' => 'error', 'message' => 'Job not found'], 404);
        }

        $progress = $job->getJsonData();
        $progress['status'] = $job->status;
        $progress['error'] = $job->error;

        return response()->json($progress);
    }

    public function blacklistCancelImport(Request $request, $job_uid)
    {
        $job = \App\Model\JobMonitor::findByUid($job_uid);
        if (!$job) {
            return response()->json(['status' => 'error', 'message' => 'Job not found'], 404);
        }

        $job->cancel();
        return response()->json(['status' => 'success']);
    }

    // ─── Signatures ─────────────────────────────────────────────

    public function signatures(Request $request)
    {
        if (Setting::get('signature.enabled') != 'yes') {
            return $this->notAuthorized();
        }

        $customer = $request->user()->customer;
        $total = $customer->signatures()->count();

        return view('refactor.pages.sending.signatures', [
            'customer' => $customer,
            'total' => $total,
        ]);
    }

    public function signaturesListing(Request $request)
    {
        $customer = $request->user()->customer;
        $signatures = $customer->signatures()
            ->search($request->keyword)
            ->orderBy($request->sort_order ?? 'created_at', $request->sort_direction ?? 'asc')
            ->paginate($request->per_page ?? 25);

        return view('refactor.pages.sending._signatures_list', [
            'signatures' => $signatures,
        ]);
    }

    public function signaturesDelete(Request $request)
    {
        $uids = is_array($request->uids) ? $request->uids : explode(',', $request->uids);
        $deleted = 0;
        foreach (Signature::whereIn('uid', $uids)->get() as $item) {
            if ($request->user()->customer->can('delete', $item)) {
                $item->delete();
                $deleted++;
            }
        }

        return response()->json(['message' => $deleted . ' ' . trans('refactor/sending.signatures.flash_deleted')]);
    }

    public function signatureCreate(Request $request)
    {
        if (Setting::get('signature.enabled') != 'yes') {
            return $this->notAuthorized();
        }

        $customer = $request->user()->customer;
        if (!$customer->can('create', Signature::class)) {
            return $this->notAuthorized();
        }

        $signature = $customer->local()->newSignature();

        return view('refactor.pages.sending.signature_form', [
            'mode'      => 'create',
            'signature' => $signature,
        ]);
    }

    public function signatureStore(Request $request)
    {
        if (Setting::get('signature.enabled') != 'yes') {
            return $this->notAuthorized();
        }

        $customer = $request->user()->customer;
        if (!$customer->can('create', Signature::class)) {
            return $this->notAuthorized();
        }

        $signature = $customer->local()->newSignature();
        [$result, $errors] = $signature->saveSignature(
            $request->name,
            $request->content,
            (bool) $request->is_default,
        );

        if (!$result) {
            return back()->withErrors($errors)->withInput();
        }

        return redirect()->route('refactor.sending.signatures')
            ->with('alert-success', trans('refactor/sending.signatures.flash_created', ['name' => $signature->name]));
    }

    public function signatureEdit(Request $request, $uid)
    {
        if (Setting::get('signature.enabled') != 'yes') {
            return $this->notAuthorized();
        }

        $customer = $request->user()->customer;
        $signature = Signature::findByUid($uid);

        if (!$signature || !$customer->can('update', $signature)) {
            return $this->notAuthorized();
        }

        return view('refactor.pages.sending.signature_form', [
            'mode'      => 'edit',
            'signature' => $signature,
        ]);
    }

    public function signatureUpdate(Request $request, $uid)
    {
        if (Setting::get('signature.enabled') != 'yes') {
            return $this->notAuthorized();
        }

        $customer = $request->user()->customer;
        $signature = Signature::findByUid($uid);

        if (!$signature || !$customer->can('update', $signature)) {
            return $this->notAuthorized();
        }

        [$result, $errors] = $signature->saveSignature(
            $request->name,
            $request->content,
            (bool) $request->is_default,
        );

        if (!$result) {
            return back()->withErrors($errors)->withInput();
        }

        return redirect()->route('refactor.sending.signatures')
            ->with('alert-success', trans('refactor/sending.signatures.flash_updated', ['name' => $signature->name]));
    }

    public function signatureSetDefault(Request $request, $uid)
    {
        $customer = $request->user()->customer;
        $signature = Signature::findByUid($uid);

        if (!$signature || !$customer->can('update', $signature)) {
            return response()->json(['status' => 'error', 'message' => trans('messages.not_authorized')], 403);
        }

        $signature->setDefault();

        return response()->json([
            'status'  => 'success',
            'message' => trans('refactor/sending.signatures.flash_set_default', ['name' => $signature->name]),
        ]);
    }

    public function signatureEnable(Request $request, $uid)
    {
        $customer = $request->user()->customer;
        $signature = Signature::findByUid($uid);

        if (!$signature || !$customer->can('enable', $signature)) {
            return response()->json(['status' => 'error', 'message' => trans('messages.not_authorized')], 403);
        }

        $signature->enable();

        return response()->json([
            'status'  => 'success',
            'message' => trans('refactor/sending.signatures.flash_enabled', ['name' => $signature->name]),
        ]);
    }

    public function signatureDisable(Request $request, $uid)
    {
        $customer = $request->user()->customer;
        $signature = Signature::findByUid($uid);

        if (!$signature || !$customer->can('disable', $signature)) {
            return response()->json(['status' => 'error', 'message' => trans('messages.not_authorized')], 403);
        }

        $signature->disable();

        return response()->json([
            'status'  => 'success',
            'message' => trans('refactor/sending.signatures.flash_disabled', ['name' => $signature->name]),
        ]);
    }

    // ─── Generic Delete Helpers ─────────────────────────────────

    public function serversDelete(Request $request)
    {
        $customer = $request->user()->customer;
        if (!EntitlementGate::allows($customer, EntitlementKey::HAS_OWN_SENDING_SERVER)) {
            return $this->notAuthorized();
        }

        $uids = is_array($request->uids) ? $request->uids : explode(',', $request->uids);
        $deleted = 0;
        foreach (SendingServer::whereIn('uid', $uids)->get() as $item) {
            if ($customer->can('delete', $item)) {
                $item->delete();
                $deleted++;
            }
        }

        return response()->json(['message' => $deleted . ' ' . trans('refactor/sending.servers.flash_deleted')]);
    }

    public function domainsDelete(Request $request)
    {
        $uids = is_array($request->uids) ? $request->uids : explode(',', $request->uids);
        $deleted = 0;
        foreach (SendingIdentity::whereIn('uid', $uids)->get() as $item) {
            if ($request->user()->customer->can('delete', $item)) {
                $item->delete();
                $deleted++;
            }
        }

        return response()->json(['message' => $deleted . ' ' . trans('refactor/sending.domains.flash_deleted')]);
    }

    public function domainCreate(Request $request)
    {
        $customer = $request->user()->customer;
        if (!$customer->can('create', SendingIdentity::class)) {
            return $this->notAuthorized();
        }

        // Build a transient model just for the form view.
        $domain = new SendingIdentity([
            'kind'              => IdentityKind::DOMAIN,
            'management_mode'   => ManagementMode::LOCAL_DKIM,
            'status'            => IdentityStatus::PENDING,
            'signing_enabled'   => true,
        ]);
        $domain->uid = '0';

        return view('refactor.pages.sending.domain_form', [
            'domain' => $domain,
            'mode'   => 'create',
        ]);
    }

    public function domainStore(Request $request, IdentityCreationService $creator)
    {
        $customer = $request->user()->customer;
        if (!$customer->can('create', SendingIdentity::class)) {
            return response()->json(['status' => 'error', 'message' => trans('messages.not_authorized')], 403);
        }

        // Plan quota gate — refuse if customer is at or over MAX_SENDING_DOMAINS.
        // Distinguish "feature not on plan" (false) from "feature on plan but cap reached"
        // (int 0 = exhausted, or int N = used >= N) — they need different upsell copy.
        $quotas = app(\App\Services\Plans\Quotas\QuotasService::class);
        if (!$quotas->canCreate($customer, \App\Services\Plans\Quotas\QuotaKey::MAX_SENDING_DOMAINS)) {
            $limit = $quotas->limit($customer, \App\Services\Plans\Quotas\QuotaKey::MAX_SENDING_DOMAINS);
            if ($limit === false) {
                // Plan does not grant MAX_SENDING_DOMAINS at all.
                $msg = trans('refactor/sending.domains.not_on_plan');
            } else {
                // Plan grants it but cap reached (0 = exhausted, or N where used >= N).
                $used = $quotas->used($customer, \App\Services\Plans\Quotas\QuotaKey::MAX_SENDING_DOMAINS);
                $msg  = trans('refactor/sending.domains.quota_exceeded', [
                    'used'  => $used,
                    'limit' => $limit ?? '∞', // $limit is int here (incl 0); null already returned true from canCreate
                ]);
            }
            return response()->json([
                'status'  => 'error',
                'message' => $msg,
                'errors'  => ['name' => [$msg]],
            ], 422);
        }

        $name = trim((string) $request->input('name', ''));
        $validator = \Validator::make(
            ['name' => $name],
            ['name' => ['required', 'regex:' . SendingIdentity::VALIDATION_REGEXP]],
        );
        if ($validator->fails()) {
            return response()->json(['status' => 'error', 'errors' => $validator->errors()], 422);
        }

        // The Sending Domains page is the entry point for LOCAL_DKIM identities
        // (server-less, Acelle-signed). If the customer's plan binds a vendor
        // server with SupportsRemoteDomainVerify, also push to that vendor.
        $subscription = $customer->getCurrentActiveSubscription();
        $plan = $subscription ? $subscription->plan : null;
        $vendorServer = ($plan && $plan->useSystemSendingServer()) ? $plan->primarySendingServer() : null;
        $useVendor = $vendorServer
            && $vendorServer->driver() instanceof \App\SendingServers\Capabilities\SupportsRemoteDomainVerify;
        $value = strtolower($name);

        $existingQuery = SendingIdentity::query()
            ->where('kind', IdentityKind::DOMAIN->value)
            ->where('value', $value);
        if ($useVendor) {
            $existingQuery->where('sending_server_id', $vendorServer->id);
        } else {
            $existingQuery->whereNull('sending_server_id')
                ->where('customer_id', $customer->local()->id);
        }
        $existing = $existingQuery->first();
        if ($existing) {
            $ownerUid = $existing->customer?->getAttribute('uid');
            $msg = $ownerUid
                ? trans('refactor/sending.domains.flash_duplicate_with_owner', ['uid' => $ownerUid])
                : trans('refactor/sending.domains.flash_duplicate');
            return response()->json([
                'status' => 'error',
                'errors' => ['name' => [$msg]],
            ], 422);
        }

        try {
            $domain = $creator->create(
                server: $useVendor ? $vendorServer : null,
                kind: IdentityKind::DOMAIN,
                value: $value,
                customer: $customer->local(),
            );
        } catch (\Illuminate\Database\QueryException $e) {
            if (($e->errorInfo[1] ?? null) === 1062) {
                return response()->json([
                    'status' => 'error',
                    'errors' => ['name' => [trans('refactor/sending.domains.flash_duplicate')]],
                ], 422);
            }
            throw $e;
        } catch (\InvalidArgumentException $e) {
            if (str_starts_with($e->getMessage(), 'Duplicate server-less identity')) {
                return response()->json([
                    'status' => 'error',
                    'errors' => ['name' => [trans('refactor/sending.domains.flash_duplicate')]],
                ], 422);
            }
            throw $e;
        }

        return response()->json([
            'status'   => 'success',
            'message'  => trans('refactor/sending.domains.flash_created'),
            'redirect' => route('refactor.sending.domain_show', $domain->uid),
        ]);
    }

    public function domainShow(Request $request, $uid)
    {
        $domain = SendingIdentity::where('uid', $uid)->first();
        if (!$domain) {
            abort(404);
        }

        $customer = $request->user()->customer;
        if (!$customer->can('read', $domain)) {
            return $this->notAuthorized();
        }

        return view('refactor.pages.sending.domain_show', [
            'domain' => $domain,
        ]);
    }

    // No domainEdit / domainUpdate — sending domains can't be modified post-
    // creation. The `value` is bound to the locally-generated DKIM keypair
    // and the customer's DNS records; editing would silently break the chain.
    // To change: delete and re-add.

    public function domainVerify(Request $request, $uid)
    {
        $domain = SendingIdentity::where('uid', $uid)->first();
        if (!$domain) {
            return response()->json(['status' => 'error', 'message' => 'Not found'], 404);
        }

        $domain->verify();

        $verified = $domain->status === IdentityStatus::VERIFIED;

        return response()->json([
            'status'   => 'success',
            'verified' => $verified,
            'message'  => $verified
                ? trans('refactor/sending.domains.verify_success')
                : trans('refactor/sending.domains.verify_pending'),
        ]);
    }

    public function domainRecords(Request $request, $uid)
    {
        $domain = SendingIdentity::where('uid', $uid)->first();
        if (!$domain) {
            abort(404);
        }

        // Group records by purpose for the legacy view shape.
        $identity = ($r = ($domain->recordsByPurpose(RecordPurpose::OWNERSHIP)[0] ?? null)) ? $r->toArray() : null;
        $dkims = array_map(fn ($r) => $r->toArray(), $domain->recordsByPurpose(RecordPurpose::DKIM));
        $spf = ($r = ($domain->recordsByPurpose(RecordPurpose::SPF)[0] ?? null)) ? $r->toArray() : null;
        $dmarc = ($r = ($domain->recordsByPurpose(RecordPurpose::DMARC)[0] ?? null)) ? $r->toArray() : null;

        Hook::collect('filter_aws_ses_dns_records', [&$identity, &$dkims, &$spf]);

        return view('refactor.pages.sending._domain_records', [
            'domain'   => $domain,
            'identity' => $identity,
            'dkims'    => $dkims,
            'spf'      => $spf,
            'dmarc'    => $dmarc,
        ]);
    }

    public function sendersDelete(Request $request)
    {
        $uids = is_array($request->uids) ? $request->uids : explode(',', $request->uids);
        $deleted = 0;
        foreach (Sender::whereIn('uid', $uids)->get() as $item) {
            if ($request->user()->customer->can('delete', $item)) {
                $item->delete();
                $deleted++;
            }
        }

        return response()->json(['message' => $deleted . ' ' . trans('refactor/sending.senders.flash_deleted')]);
    }

    public function senderCreate(Request $request)
    {
        $customer = $request->user()->customer;
        if (Gate::denies('create', new Sender())) {
            return $this->notAuthorized();
        }

        $sender = new Sender();

        return view('refactor.pages.sending.sender_form', [
            'sender' => $sender,
            'mode' => 'create',
        ]);
    }

    public function senderStore(Request $request)
    {
        $customer = $request->user()->customer;
        if (Gate::denies('create', new Sender())) {
            return response()->json(['status' => 'error', 'message' => trans('messages.not_authorized')], 403);
        }

        $sender = new Sender();
        $sender->fill($request->all());
        $sender->customer_id = $customer->id;
        $sender->status = Sender::STATUS_PENDING;

        $validator = \Validator::make($request->all(), $sender->rules());
        if ($validator->fails()) {
            return response()->json(['status' => 'error', 'errors' => $validator->errors()], 422);
        }

        $sender->save();

        // Determine sending server for verification
        $server = null;
        $plan = $customer->getCurrentActiveSubscription()?->plan;
        if ($plan && $plan->useSystemSendingServer()) {
            $server = $plan->primarySendingServer();
        }

        try {
            $sender->verifyWith($server);
        } catch (\Exception $e) {
            // Verification email may fail but sender is saved
        }

        return response()->json([
            'status' => 'success',
            'message' => trans('refactor/sending.senders.flash_created'),
            'redirect' => route('refactor.sending.sender_show', $sender->uid),
        ]);
    }

    public function senderShow(Request $request, $uid)
    {
        $sender = Sender::findByUid($uid);
        if (!$sender) {
            abort(404);
        }

        $customer = $request->user()->customer;
        if (!$customer->can('read', $sender)) {
            return $this->notAuthorized();
        }

        // Refresh verification status — remote SES/SMTP call may fail (network, credentials).
        // Soft-failure: page still renders with last-known status. Log so ops can spot patterns.
        try {
            $sender->updateVerificationStatus();
        } catch (\Throwable $e) {
            \Log::warning('Sender verification refresh failed', [
                'sender_id' => $sender->id,
                'sender_email' => $sender->email,
                'error' => $e->getMessage(),
            ]);
        }

        return view('refactor.pages.sending.sender_show', [
            'sender' => $sender,
        ]);
    }

    public function senderEdit(Request $request, $uid)
    {
        $sender = Sender::findByUid($uid);
        if (!$sender) {
            abort(404);
        }

        $customer = $request->user()->customer;
        if (!$customer->can('update', $sender)) {
            return $this->notAuthorized();
        }

        return view('refactor.pages.sending.sender_form', [
            'sender' => $sender,
            'mode' => 'edit',
        ]);
    }

    public function senderUpdate(Request $request, $uid)
    {
        $sender = Sender::findByUid($uid);
        if (!$sender) {
            return response()->json(['status' => 'error', 'message' => 'Not found'], 404);
        }

        $customer = $request->user()->customer;
        if (!$customer->can('update', $sender)) {
            return response()->json(['status' => 'error', 'message' => trans('messages.not_authorized')], 403);
        }

        $validator = \Validator::make($request->all(), $sender->editRules());
        if ($validator->fails()) {
            return response()->json(['status' => 'error', 'errors' => $validator->errors()], 422);
        }

        $sender->name = $request->name;
        $sender->save();

        return response()->json([
            'status' => 'success',
            'message' => trans('refactor/sending.senders.flash_updated'),
            'redirect' => route('refactor.sending.sender_show', $sender->uid),
        ]);
    }

    public function trackingDomainsDelete(Request $request)
    {
        $uids = is_array($request->uids) ? $request->uids : explode(',', $request->uids);
        $deleted = 0;
        foreach (TrackingDomain::whereIn('uid', $uids)->get() as $item) {
            if ($request->user()->customer->can('delete', $item)) {
                $item->delete();
                $deleted++;
            }
        }

        return response()->json(['message' => $deleted . ' ' . trans('refactor/sending.tracking.flash_deleted')]);
    }

    public function verificationServersDelete(Request $request)
    {
        $uids = is_array($request->uids) ? $request->uids : explode(',', $request->uids);
        $deleted = 0;
        foreach (EmailVerificationServer::whereIn('uid', $uids)->get() as $item) {
            if ($request->user()->customer->can('delete', $item)) {
                $item->delete();
                $deleted++;
            }
        }

        return response()->json(['message' => $deleted . ' ' . trans('refactor/sending.verification_servers.flash_deleted')]);
    }

    public function verificationServerCreate(Request $request)
    {
        $server = new EmailVerificationServer();
        $server->status = EmailVerificationServer::STATUS_ACTIVE;

        if (!$request->user()->customer->can('create', $server)) {
            return $this->notAuthorized();
        }

        return view('refactor.pages.sending.verification_server_form', [
            'server' => $server,
            'services' => $this->buildVerificationServicesList(),
            'options' => [],
            'isEdit' => false,
        ]);
    }

    /**
     * Build the {id, name, fields[]} list the form JS expects, from DriverRegistry.
     * Replaces legacy config('verification.services') shape.
     */
    private function buildVerificationServicesList(): array
    {
        $services = [];
        foreach (\App\Verification\DriverRegistry::all() as $type => $driverClass) {
            $cols = $driverClass::validationRules()['cols'] ?? [];
            // Vendor-specific fields only; rate-limit fields are rendered separately.
            $fields = array_values(array_filter(array_keys($cols), fn ($k) => !str_starts_with($k, 'limit_')));
            $services[] = [
                'id'     => $type,
                'name'   => $driverClass::displayName(),
                'fields' => $fields,
            ];
        }
        return $services;
    }

    public function verificationServerStore(Request $request)
    {
        $server = new EmailVerificationServer();
        if (!$request->user()->customer->can('create', $server)) {
            return $this->notAuthorized();
        }

        $server->fill($request->all());

        $validator = \Validator::make($request->all(), $server->rules());
        if ($validator->fails()) {
            return response()->json(['status' => 'error', 'errors' => $validator->errors()], 422);
        }

        $server->customer_id = $request->user()->customer->id;
        $server->status = EmailVerificationServer::STATUS_ACTIVE;
        $server->options = $request->options ?? []; // $casts['options' => 'array'] handles JSON encoding
        $server->save();
        (new \App\ActivityLog\Activities\EmailVerificationServerCreated($server, $request->user()))->record();

        return response()->json([
            'status' => 'success',
            'message' => trans('messages.email_verification_server.created'),
            'redirect' => route('refactor.sending.verification_servers'),
        ]);
    }

    public function verificationServerEdit(Request $request, $uid)
    {
        $server = EmailVerificationServer::findByUid($uid);
        if (!$request->user()->customer->can('update', $server)) {
            return $this->notAuthorized();
        }

        return view('refactor.pages.sending.verification_server_form', [
            'server'   => $server,
            'services' => $this->buildVerificationServicesList(),
            'options'  => $server->options ?? [],
            'isEdit'   => true,
        ]);
    }

    public function verificationServerUpdate(Request $request, $uid)
    {
        $server = EmailVerificationServer::findByUid($uid);
        if (!$request->user()->customer->can('update', $server)) {
            return $this->notAuthorized();
        }

        $server->fill($request->all());

        $validator = \Validator::make($request->all(), $server->rules());
        if ($validator->fails()) {
            return response()->json(['status' => 'error', 'errors' => $validator->errors()], 422);
        }

        $server->options = $request->options ?? []; // $casts['options' => 'array'] handles JSON encoding
        $server->save();
        (new \App\ActivityLog\Activities\EmailVerificationServerUpdated($server, $request->user()))->record();

        return response()->json([
            'status' => 'success',
            'message' => trans('messages.email_verification_server.updated'),
            'redirect' => route('refactor.sending.verification_server_edit', $server->uid),
        ]);
    }

    public function verificationServerEnable(Request $request, $uid)
    {
        $server = EmailVerificationServer::findByUid($uid);
        if (!$request->user()->customer->can('enable', $server)) {
            return response()->json(['status' => 'error', 'message' => 'Not authorized'], 403);
        }

        $server->enable();

        return response()->json([
            'status' => 'success',
            'message' => trans('messages.email_verification_servers.enabled'),
        ]);
    }

    public function verificationServerDisable(Request $request, $uid)
    {
        $server = EmailVerificationServer::findByUid($uid);
        if (!$request->user()->customer->can('disable', $server)) {
            return response()->json(['status' => 'error', 'message' => 'Not authorized'], 403);
        }

        $server->disable();

        return response()->json([
            'status' => 'success',
            'message' => trans('messages.email_verification_servers.disabled'),
        ]);
    }

    // ─── Server CRUD ────────────────────────────────────────────

    public function serverSelect(Request $request)
    {
        $customer = $request->user()->customer;
        if (!EntitlementGate::allows($customer, EntitlementKey::HAS_OWN_SENDING_SERVER)) {
            return $this->notAuthorized();
        }
        if (!$customer->can('create', SendingServer::class)) {
            return $this->notAuthorized();
        }

        $types = $customer->getSendingServertypes();

        // Plugin-registered types come in via the same `register_sending_server_driver`
        // hook used by core. Picker metadata (name/description/icon_url) lives on
        // the hook payload — plugins can't push translations into the main app's
        // lang file, so the view renders these fields directly instead of through
        // trans('refactor/sending.servers.type.X').
        $pluginMeta = [];
        foreach (\App\Library\Facades\Hook::collect('register_sending_server_driver') as $meta) {
            if (!is_array($meta) || !isset($meta['type']) || !isset($meta['name'])) {
                continue;
            }
            $pluginMeta[$meta['type']] = [
                'name'        => (string) $meta['name'],
                'description' => (string) ($meta['description'] ?? ''),
                'icon_url'    => (string) ($meta['icon_url'] ?? ''),
            ];
        }

        return view('refactor.pages.sending.server_select', [
            'types'      => $types,
            'pluginMeta' => $pluginMeta,
        ]);
    }

    public function serverCreate(Request $request)
    {
        $customer = $request->user()->customer;
        if (!EntitlementGate::allows($customer, EntitlementKey::HAS_OWN_SENDING_SERVER)) {
            return $this->notAuthorized();
        }
        if (!$customer->can('create', SendingServer::class)) {
            return $this->notAuthorized();
        }

        $type = $request->type;
        if (!array_key_exists($type, $customer->getSendingServertypes())) {
            abort(403);
        }

        $server = SendingServer::newOfType($type);

        return view('refactor.pages.sending.server_create', [
            'server' => $server,
            'type' => $type,
            'awsRegions' => SendingServer::awsRegionSelectOptions(),
            'mailgunRegions' => SendingServer::mailgunRegionSelectOptions(),
        ]);
    }

    public function serverStore(Request $request)
    {
        $customer = $request->user()->customer;
        if (!EntitlementGate::allows($customer, EntitlementKey::HAS_OWN_SENDING_SERVER)) {
            return $this->notAuthorized();
        }
        if (!$customer->can('create', SendingServer::class)) {
            return $this->notAuthorized();
        }

        [$validator, $server] = SendingServer::createFromArray(array_merge($request->all(), [
            'customer_id' => $customer->id,
        ]));

        if ($validator->fails()) {
            return redirect()->back()->withErrors($validator)->withInput();
        }

        return redirect()->route('refactor.sending.server_edit', $server->uid)
            ->with('success', trans('refactor/sending.servers.created'));
    }

    public function serverEdit(Request $request, $uid)
    {
        $customer = $request->user()->customer;
        if (!EntitlementGate::allows($customer, EntitlementKey::HAS_OWN_SENDING_SERVER)) {
            return $this->notAuthorized();
        }

        $server = $customer->sendingServers()->where('uid', $uid)->firstOrFail();

        if (!$customer->can('update', $server)) {
            return $this->notAuthorized();
        }

        return view('refactor.pages.sending.server_edit', [
            'server' => $server,
            'bounceHandlers' => BounceHandler::getSelectOptions(),
            'feedbackHandlers' => FeedbackLoopHandler::getSelectOptions(),
            'awsRegions' => SendingServer::awsRegionSelectOptions(),
            'mailgunRegions' => SendingServer::mailgunRegionSelectOptions(),
            'sendingLimitOptions' => $server->getSendingLimitSelectOptions(),
        ]);
    }

    /**
     * Save the Connection card on the customer-side sending server edit page —
     * driver-specific credentials (host, smtp_*, api_key, etc).
     */
    public function serverConnectionSave(Request $request, $uid)
    {
        $customer = $request->user()->customer;
        if (!EntitlementGate::allows($customer, EntitlementKey::HAS_OWN_SENDING_SERVER)) {
            return $this->notAuthorized();
        }

        $server = $customer->sendingServers()->where('uid', $uid)->firstOrFail();

        if (!$customer->can('update', $server)) {
            return $this->notAuthorized();
        }

        $validator = \Validator::make($request->all(), $server->getRules());
        if ($validator->fails()) {
            return response()->json(['errors' => $validator->errors()], 422);
        }

        $server->fill($request->all());
        $server->save();

        return response()->json(['message' => trans('refactor/sending.servers.updated')]);
    }

    /**
     * Save the Configuration card on the customer-side sending server edit page —
     * name, default_from_email, handler IDs, sending_limit + quota_*.
     * Customer-side has no Identity/Warmup tabs (admin-only), so this scope
     * covers all non-Connection settings the customer can edit.
     */
    public function serverConfigurationSave(Request $request, $uid)
    {
        $customer = $request->user()->customer;
        if (!EntitlementGate::allows($customer, EntitlementKey::HAS_OWN_SENDING_SERVER)) {
            return $this->notAuthorized();
        }

        $server = $customer->sendingServers()->where('uid', $uid)->firstOrFail();

        if (!$customer->can('update', $server)) {
            return $this->notAuthorized();
        }

        $validator = \Validator::make($request->all(), $server->getConfigRules());
        if ($validator->fails()) {
            return response()->json(['errors' => $validator->errors()], 422);
        }

        $server->fill($request->only(['name', 'default_from_email']));
        $server->bounce_handler_id = $request->bounce_handler_id ?: null;
        $server->feedback_loop_handler_id = $request->feedback_loop_handler_id ?: null;

        if ($request->options) {
            $server->setOptions($request->options);
        }

        $limitKey = $request->input('options.sending_limit');
        if ($limitKey && $limitKey !== 'custom' && $limitKey !== 'current') {
            $limits = SendingServer::sendingLimitValues()[$limitKey] ?? null;
            if ($limits) {
                $server->quota_value = $limits['quota_value'];
                $server->quota_unit = $limits['quota_unit'];
                $server->quota_base = $limits['quota_base'];
            }
        } elseif ($limitKey === 'custom') {
            $server->quota_value = $request->quota_value;
            $server->quota_unit = $request->quota_unit;
            $server->quota_base = $request->quota_base;
        }

        $server->save();

        return response()->json(['message' => trans('refactor/sending.servers.config_saved')]);
    }

    public function serverEnable(Request $request, $uid)
    {
        $customer = $request->user()->customer;
        if (!EntitlementGate::allows($customer, EntitlementKey::HAS_OWN_SENDING_SERVER)) {
            return $this->notAuthorized();
        }

        $server = $customer->sendingServers()->where('uid', $uid)->firstOrFail();

        if (!$customer->can('update', $server)) {
            return $this->notAuthorized();
        }

        $server->enable();

        return response()->json(['message' => trans('refactor/sending.servers.enabled')]);
    }

    public function serverDisable(Request $request, $uid)
    {
        $customer = $request->user()->customer;
        if (!EntitlementGate::allows($customer, EntitlementKey::HAS_OWN_SENDING_SERVER)) {
            return $this->notAuthorized();
        }

        $server = $customer->sendingServers()->where('uid', $uid)->firstOrFail();

        if (!$customer->can('update', $server)) {
            return $this->notAuthorized();
        }

        $server->disable();

        return response()->json(['message' => trans('refactor/sending.servers.disabled')]);
    }

    public function serverTest(Request $request, $uid)
    {
        $customer = $request->user()->customer;
        if (!EntitlementGate::allows($customer, EntitlementKey::HAS_OWN_SENDING_SERVER)) {
            return $this->notAuthorized();
        }

        $server = $customer->sendingServers()->where('uid', $uid)->firstOrFail();

        return view('refactor.pages.sending._server_test', [
            'server' => $server,
        ]);
    }

    public function serverTestSend(Request $request, $uid)
    {
        $customer = $request->user()->customer;
        if (!EntitlementGate::allows($customer, EntitlementKey::HAS_OWN_SENDING_SERVER)) {
            return $this->notAuthorized();
        }

        $server = $customer->sendingServers()->where('uid', $uid)->firstOrFail();

        $validator = \Validator::make($request->all(), ['email' => 'required|email']);
        if ($validator->fails()) {
            return response()->json(['message' => $validator->errors()->first()], 422);
        }

        try {
            $email = new \Symfony\Component\Mime\Email();
            $msgId = \App\Library\StringHelper::generateMessageId(
                \App\Library\StringHelper::getDomainFromEmail($request->user()->email)
            );
            $email->getHeaders()->addIdHeader('Message-ID', $msgId);
            $email->getHeaders()->addTextHeader('X-Acelle-Message-Id', $msgId);
            $email->subject('Test email from ' . config('app.name'));
            $email->from($request->user()->email);
            $email->to($request->email);
            $email->text('This is a test email sent from your sending server to verify delivery is working.');

            $server->sendTestEmail($email);

            return response()->json(['message' => trans('refactor/sending.servers.test_sent')]);
        } catch (\Exception $e) {
            return response()->json(['message' => $e->getMessage()], 422);
        }
    }

    public function serverTestConnection(Request $request, $uid)
    {
        $customer = $request->user()->customer;
        if (!EntitlementGate::allows($customer, EntitlementKey::HAS_OWN_SENDING_SERVER)) {
            return $this->notAuthorized();
        }

        $server = $customer->sendingServers()->where('uid', $uid)->firstOrFail();

        if (!$customer->can('update', $server)) {
            return $this->notAuthorized();
        }

        // F4.1 (P11) — same service the `sending.test_connection` AI tool
        // calls. Single source of truth for the probe on customer side.
        $result = app(\App\Services\SendingServerHealthService::class)->testConnection($server);
        if ($result['success']) {
            return response()->json(['message' => trans('refactor/sending.servers.test_connection_ok')]);
        }

        return response()->json(['message' => $result['error_message'] ?? ''], 422);
    }

    // ─── Sender Verify Token (public, no auth) ──────────────────

    public function senderVerify(Request $request, $token)
    {
        try {
            $sender = \App\Model\Sender::verifyToken($token);
            return view('refactor.pages.sending.sender_verified', compact('sender'));
        } catch (\Exception $ex) {
            return view('refactor.pages.sending.sender_failed', [
                'message' => $ex->getMessage(),
            ]);
        }
    }
}