<?php

namespace App\Domain\Shared\Services;

use App\Domain\Shared\Branding\BrandProfile;
use App\Domain\Shared\Branding\ColorRamp;
use App\Domain\Shared\Models\Academy;
use App\Domain\Shared\Models\SystemSetting;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;

/**
 * The single read API for a tenant's brand — admin, portal, website, print.
 *
 * Every layout used to resolve branding itself in an `@php` block, one
 * SettingsService::get() per field. Each of those is its own SELECT with no
 * memoisation, so an admin page render issued about sixteen of them and did it
 * again on every Livewire round-trip. Worse, each block picked its own
 * fallback, which is how primary_color came to have three different defaults
 * in three files.
 *
 * A whole profile is cached under the academy's `branding_version`, so it is
 * held until branding actually changes rather than for a guessed number of
 * minutes, and `bump()` on save invalidates it everywhere at once — including
 * the queue workers and the scheduler, which a request-scoped memo would miss.
 */
class BrandingService
{
    /** @var array<int, BrandProfile> per-request memo on top of the cache */
    private array $memo = [];

    private const COLOR_DEFAULTS = [
        'primary_color' => '#2563eb',
        'secondary_color' => '#7c3aed',
        'accent_color' => '#059669',
        'sidebar_bg' => '#0f172a',
        'sidebar_text' => '#e2e8f0',
        'sidebar_active' => '#2563eb',
        'header_bg' => '#ffffff',
        'success_color' => '#10b981',
        'warning_color' => '#f59e0b',
        'danger_color' => '#ef4444',
        'theme_color' => '#2563eb',
    ];

    public function forCurrentAcademy(): BrandProfile
    {
        $academy = app()->has('current_academy') ? app('current_academy') : null;

        return $this->for($academy?->id);
    }

    public function for(?int $academyId): BrandProfile
    {
        if (! $academyId) {
            return $this->fallbackProfile();
        }

        if (isset($this->memo[$academyId])) {
            return $this->memo[$academyId];
        }

        $academy = Academy::find($academyId);

        if (! $academy) {
            return $this->fallbackProfile();
        }

        $version = (int) ($academy->branding_version ?? 1);

        return $this->memo[$academyId] = Cache::rememberForever(
            "branding:{$academyId}:v{$version}",
            fn () => $this->build($academy, $version)
        );
    }

    /**
     * Invalidate every cached copy of an academy's brand.
     *
     * Bumping the version rather than forgetting a key means a worker holding
     * a stale profile misses on its next read instead of serving last week's
     * colours, and it needs no coordination between processes.
     */
    public function bump(int $academyId): void
    {
        unset($this->memo[$academyId]);

        Academy::whereKey($academyId)->increment('branding_version');
    }

    private function build(Academy $academy, int $version): BrandProfile
    {
        $settings = $this->loadBrandingGroup($academy->id);

        $colors = [];
        foreach (self::COLOR_DEFAULTS as $key => $default) {
            $colors[$key] = $this->hex($settings["branding.{$key}"] ?? null, $default);
        }

        $ramps = [
            'primary' => ColorRamp::fromHex($colors['primary_color']),
            'secondary' => ColorRamp::fromHex($colors['secondary_color']),
            'accent' => ColorRamp::fromHex($colors['accent_color']),
        ];

        return new BrandProfile(
            academyId: $academy->id,
            version: $version,
            academyName: (string) ($settings['branding.academy_name'] ?? '') ?: ($academy->name_ar ?: $academy->name),
            appShortName: (string) ($settings['branding.app_short_name'] ?? '') ?: ($academy->name_ar ?: $academy->name),
            address: $academy->address ?: null,
            phone: $academy->phone ?: null,
            email: $academy->email ?: null,

            logoUrl: $this->url($settings['branding.logo'] ?? $academy->logo_path),
            logoDarkUrl: $this->url($settings['branding.logo_dark'] ?? null),
            faviconUrl: $this->url($settings['branding.favicon'] ?? null),
            signatureUrl: $this->url($settings['branding.signature'] ?? null),
            loginBackgroundUrl: $this->url($settings['branding.login_background'] ?? null),
            invoiceHeaderUrl: $this->url($settings['branding.invoice_header'] ?? null),
            appIconUrl: $this->url($settings['branding.app_icon'] ?? null),
            splashImageUrl: $this->url($settings['branding.splash_image'] ?? null),

            primary: $colors['primary_color'],
            secondary: $colors['secondary_color'],
            accent: $colors['accent_color'],
            sidebarBg: $colors['sidebar_bg'],
            sidebarText: $colors['sidebar_text'],
            sidebarActive: $colors['sidebar_active'],
            headerBg: $colors['header_bg'],
            success: $colors['success_color'],
            warning: $colors['warning_color'],
            danger: $colors['danger_color'],
            themeColor: $colors['theme_color'],
            themeMode: in_array($settings['branding.theme_mode'] ?? 'light', ['light', 'dark', 'auto'], true)
                ? (string) $settings['branding.theme_mode']
                : 'light',

            fontAr: $this->fontName($settings['branding.font_family_ar'] ?? null, 'Cairo'),
            fontEn: $this->fontName($settings['branding.font_family_en'] ?? null, 'Inter'),
            fontSizeBase: max(12, min(18, (int) ($settings['branding.font_size_base'] ?? 14))),

            invoiceFooterText: (string) ($settings['branding.invoice_footer_text'] ?? ''),
            receiptFooterText: (string) ($settings['branding.receipt_footer_text'] ?? ''),
            termsAndConditions: (string) ($settings['branding.terms_and_conditions'] ?? ''),

            showLogoInSidebar: $this->bool($settings['branding.show_logo_in_sidebar'] ?? true),
            showLogoInInvoice: $this->bool($settings['branding.show_logo_in_invoice'] ?? true),
            showSignatureInInvoice: $this->bool($settings['branding.show_signature_in_invoice'] ?? false),
            compactSidebar: $this->bool($settings['branding.compact_sidebar'] ?? false),

            ramps: $ramps,
        );
    }

    /** One query for the whole group, replacing one query per field. */
    private function loadBrandingGroup(int $academyId): array
    {
        return SystemSetting::withoutGlobalScope('academy')
            ->where('academy_id', $academyId)
            ->where('group', 'branding')
            ->get()
            ->mapWithKeys(fn ($s) => [$s->key => $s->value])
            ->all();
    }

    /**
     * A stored path becomes a URL here and nowhere else. Emitting the raw path
     * is what broke the logo on every printed sheet.
     */
    private function url(?string $path): ?string
    {
        if (! $path) {
            return null;
        }

        if (str_starts_with($path, 'http://') || str_starts_with($path, 'https://') || str_starts_with($path, '/')) {
            return $path;
        }

        return Storage::disk('public')->url($path);
    }

    private function hex(?string $value, string $default): string
    {
        $value = trim((string) $value);

        return preg_match('/^#[0-9a-fA-F]{6}$/', $value) ? strtolower($value) : $default;
    }

    /**
     * The font name is interpolated into a Google Fonts URL and into a CSS
     * font-family declaration, so it may only ever be a plain font name.
     */
    private function fontName(?string $value, string $default): string
    {
        $value = trim((string) $value);

        return preg_match('/^[A-Za-z0-9 ]{1,40}$/', $value) ? $value : $default;
    }

    private function bool(mixed $value): bool
    {
        return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? (bool) $value;
    }

    private function fallbackProfile(): BrandProfile
    {
        $colors = self::COLOR_DEFAULTS;

        return new BrandProfile(
            academyId: 0,
            version: 0,
            academyName: 'الكابتن',
            appShortName: 'الكابتن',
            address: null,
            phone: null,
            email: null,
            logoUrl: null,
            logoDarkUrl: null,
            faviconUrl: null,
            signatureUrl: null,
            loginBackgroundUrl: null,
            invoiceHeaderUrl: null,
            appIconUrl: null,
            splashImageUrl: null,
            primary: $colors['primary_color'],
            secondary: $colors['secondary_color'],
            accent: $colors['accent_color'],
            sidebarBg: $colors['sidebar_bg'],
            sidebarText: $colors['sidebar_text'],
            sidebarActive: $colors['sidebar_active'],
            headerBg: $colors['header_bg'],
            success: $colors['success_color'],
            warning: $colors['warning_color'],
            danger: $colors['danger_color'],
            themeColor: $colors['theme_color'],
            themeMode: 'light',
            fontAr: 'Cairo',
            fontEn: 'Inter',
            fontSizeBase: 14,
            invoiceFooterText: '',
            receiptFooterText: '',
            termsAndConditions: '',
            showLogoInSidebar: true,
            showLogoInInvoice: true,
            showSignatureInInvoice: false,
            compactSidebar: false,
            ramps: [
                'primary' => ColorRamp::fromHex($colors['primary_color']),
                'secondary' => ColorRamp::fromHex($colors['secondary_color']),
                'accent' => ColorRamp::fromHex($colors['accent_color']),
            ],
        );
    }
}
