<?php

namespace App\Providers;

use App\Http\ViewComposers\GlobalLayoutComposer;
use App\Modules\CMS\Models\Setting;
use App\Modules\Pharmacy\Models\Prescription;
use App\Modules\Pharmacy\Policies\PrescriptionPolicy;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Rules\Password;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        if ($this->app->environment('local')) {
            // Register Telescope in local env only
            if (class_exists(\Laravel\Telescope\TelescopeServiceProvider::class)) {
                $this->app->register(\Laravel\Telescope\TelescopeServiceProvider::class);
            }
        }
    }

    public function boot(): void
    {
        // Resolve factories for modular models (App\Modules\*\Models\Foo → Database\Factories\FooFactory)
        Factory::guessFactoryNamesUsing(function (string $modelName): string {
            $baseName = class_basename($modelName);
            return "Database\\Factories\\{$baseName}Factory";
        });

        // Strict Eloquent mode in non-production
        Model::shouldBeStrict(! $this->app->isProduction());

        // Force HTTPS in production
        if ($this->app->isProduction()) {
            URL::forceScheme('https');
        }

        // Super-admin bypass — grants all Gate abilities unconditionally.
        // Referenced in RolePermissionSeeder; required for Filament CUD actions
        // on models that have no explicit policy (e.g. TherapeuticCategory).
        Gate::before(function ($user, string $ability) {
            if ($user->hasRole('super_admin')) {
                return true;
            }
        });

        // ── Email settings (Admin → Settings → Email Settings) ─────────────────
        // Applies DB-entered SMTP credentials on every boot, mirroring the
        // Payment settings pattern (PaymentServiceProvider::applyDbPaymentSettings).
        // Without this, the Email Settings page's SMTP fields only ever affected
        // the "Send Test Email" button — real order/prescription emails kept
        // going out via whatever was in .env regardless of what was saved here.
        $this->applyDbEmailSettings();

        // ── Global layout data ────────────────────────────────────────────────
        // Most storefront views use <x-app-layout> which @include('layouts.app').
        // @include does NOT trigger View Composers bound to a specific view name,
        // so we register on '*' (all views) to guarantee the data is always present.
        // The composer itself caches the DB queries per request so there is no
        // N+1 performance cost.
        View::composer('*', GlobalLayoutComposer::class);

        // Policies
        Gate::policy(Prescription::class, PrescriptionPolicy::class);

        // Global password rules
        Password::defaults(function () {
            return $this->app->isProduction()
                ? Password::min(8)->mixedCase()->numbers()->symbols()->uncompromised()
                : Password::min(8);
        });
    }

    /**
     * Admin-entered SMTP credentials (Email Settings page) live in the DB
     * `settings` table under group "email". Overlay them onto the config
     * repository here — DB wins over .env when a value is set. Falls back
     * silently to .env-only behaviour when the settings table doesn't exist
     * yet (fresh install, before migrations run) or nothing has been saved.
     */
    private function applyDbEmailSettings(): void
    {
        try {
            if (! Schema::hasTable('settings')) {
                return;
            }

            $e = Setting::group('email');
        } catch (\Throwable) {
            return;
        }

        if (empty($e)) {
            return;
        }

        $overrides = array_filter([
            'mail.mailers.smtp.host'     => $e['mail_host']       ?? null,
            'mail.mailers.smtp.port'     => $e['mail_port']       ?? null,
            'mail.mailers.smtp.username' => $e['mail_username']   ?? null,
            'mail.mailers.smtp.password' => $e['mail_password']   ?? null,
            'mail.from.name'             => $e['mail_from_name']  ?? null,
            'mail.from.address'          => $e['mail_from_email'] ?? null,
        ], fn ($v) => $v !== null && $v !== '');

        // Handled separately: "none" must become a real `null` config value
        // (Laravel's SMTP transport treats null as "no encryption"), which
        // array_filter above would otherwise strip out.
        if (isset($e['mail_encryption'])) {
            $overrides['mail.mailers.smtp.encryption'] =
                $e['mail_encryption'] === 'none' ? null : $e['mail_encryption'];
        }

        // Overlaying SMTP host/port/credentials alone does nothing unless
        // `mail.default` actually points at the `smtp` mailer. .env ships
        // MAIL_MAILER=log for local dev, and nothing else in the app ever
        // changes it — so every outgoing email (verification links, order
        // notifications, prescription approvals, and even the admin's own
        // "Send Test Email" button) was silently being written to
        // storage/logs/laravel.log instead of actually being sent, no
        // matter how correctly SMTP was configured on the Email Settings
        // page. Once an admin has entered a host here, they clearly intend
        // SMTP to be used, so switch the default mailer over too.
        if (! empty($e['mail_host'])) {
            $overrides['mail.default'] = 'smtp';
        }

        if ($overrides) {
            config($overrides);
        }
    }
}
