<?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 App\Domain\Website\Models\WebsiteSetting;
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);
        $key = "branding:{$academyId}:v{$version}";

        $profile = Cache::get($key);

        if (! $profile instanceof BrandProfile) {
            // Either nothing is cached, or what came back is not a profile —
            // which is what a cache configured to refuse this class returns.
            // Rebuild rather than hand back a corpse: a wrong-typed value in a
            // cache is not a cache hit, and letting it reach the return type
            // below turns a cache problem into a 500 on every branded page.
            $profile = $this->build($academy, $version);
            Cache::forever($key, $profile);
        }

        return $this->memo[$academyId] = $profile;
    }

    /**
     * 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');
    }

    /**
     * Brand colours the public website may speak for when branding has not.
     *
     * Left is the branding key, right is the website_settings column it
     * inherits from. Only identity colours are here: success/warning/danger
     * mean the same thing in every tenant and are not a matter of taste, and
     * the sidebar chrome is an admin-surface decision the website knows
     * nothing about.
     */
    private const WEBSITE_INHERITED = [
        'primary_color' => 'primary_color',
        'secondary_color' => 'secondary_color',
        'accent_color' => 'accent_color',
        'theme_color' => 'primary_color',
        'sidebar_active' => 'primary_color',
    ];

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

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

            /*
            A tenant that themed its public site and never opened the branding
            screen was getting our shipped blue everywhere else — the member
            portal, the PWA's theme colour, the admin's active state — while
            its own site was navy and gold. The branding form pre-fills these
            defaults and saves them verbatim, so "still at the default" is
            indistinguishable from "never chosen", and treating it as never
            chosen is the reading that produces one brand instead of two.

            Choosing the default colour deliberately, on a tenant whose website
            says otherwise, is the one case this gets wrong; picking any other
            colour settles it immediately and permanently.
            */
            if ($colors[$key] === $default && isset(self::WEBSITE_INHERITED[$key])) {
                $colors[$key] = $website[self::WEBSITE_INHERITED[$key]] ?? $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,
        );
    }

    /**
     * The public website's palette, or an empty array if it has none.
     *
     * Wrapped because this runs on the first branded render of a brand-new
     * tenant, which may be a container that has not finished migrating: a
     * missing table is a reason to fall back to the defaults, not to 500 on
     * every page.
     *
     * @return array<string, string>
     */
    private function websitePalette(int $academyId): array
    {
        try {
            $website = WebsiteSetting::withoutGlobalScope('academy')
                ->where('academy_id', $academyId)
                ->first(['primary_color', 'secondary_color', 'accent_color']);
        } catch (\Throwable) {
            return [];
        }

        if (! $website) {
            return [];
        }

        $palette = [];

        foreach (['primary_color', 'secondary_color', 'accent_color'] as $column) {
            $hex = $this->hex($website->{$column} ?? null, '');

            if ($hex !== '') {
                $palette[$column] = $hex;
            }
        }

        return $palette;
    }

    /** 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']),
            ],
        );
    }
}
