<?php

namespace App\Support;

use App\Models\Company;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;

class BrandLogoManager
{
    private ?array $appLogoCache = null;

    // ── Application (platform) logo ─────────────────────────────────────────

    public function resolveApp(string $context = 'login'): array
    {
        $asset = $this->appLogoAsset();
        $class = config("branding.app.contexts.{$context}.class")
            ?? config('branding.app.contexts.login.class', 'logo logo--app');

        return [
            'url' => $asset['url'],
            'fallback_url' => asset((string) config('branding.app.default', 'images/axis-logo.svg')),
            'alt' => (string) config('app.name'),
            'class' => $class,
        ];
    }

    public function appLogoAsset(): array
    {
        if ($this->appLogoCache !== null) {
            return $this->appLogoCache;
        }

        $absolute = $this->appLogoAbsolutePath();

        return $this->appLogoCache = [
            'url' => route('brand.app-logo', ['v' => $this->versionToken($absolute)]),
            'path' => $absolute,
        ];
    }

    public function appLogoAbsolutePath(): string
    {
        $storagePath = $this->findAppLogoStoragePath();
        if ($storagePath !== null) {
            $absolute = Storage::disk('public')->path($storagePath);
            if (is_file($absolute)) {
                return $absolute;
            }
        }

        $this->syncAppLogoStorageToPublicMirror();
        $mirrored = $this->findAppLogoPublicMirror();
        if ($mirrored !== null) {
            return $mirrored;
        }

        return public_path((string) config('branding.app.default', 'images/axis-logo.svg'));
    }

    public function hasCustomAppLogo(): bool
    {
        return $this->findAppLogoStoragePath() !== null;
    }

    public function storageIsWritable(): bool
    {
        $directory = Storage::disk('public')->path(
            trim((string) config('branding.app.storage_directory', 'branding'), '/')
        );

        if (!is_dir($directory)) {
            File::ensureDirectoryExists($directory);
        }

        return is_dir($directory) && is_writable($directory);
    }

    public function mimeTypeForPath(string $path): string
    {
        $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));

        return match ($extension) {
            'png' => 'image/png',
            'jpg', 'jpeg' => 'image/jpeg',
            'svg' => 'image/svg+xml',
            'webp' => 'image/webp',
            default => 'application/octet-stream',
        };
    }

    public function companyLogoAbsolutePath(int $companyId): ?string
    {
        $company = Company::withoutGlobalScopes()->find($companyId);
        if (!$company?->hasLogo()) {
            return null;
        }

        $storage = Storage::disk('public')->path((string) $company->logo);
        if (is_file($storage)) {
            return $storage;
        }

        $this->syncCompanyLogoToPublicMirror($company);

        return $this->findCompanyLogoPublicMirror($company);
    }

    public function appLogoStatus(): array
    {
        $storagePath = $this->findAppLogoStoragePath();
        $asset = $this->appLogoAsset();
        $absolute = $asset['path'] ?? null;

        return [
            'url' => $asset['url'],
            'is_custom' => $storagePath !== null,
            'source' => $storagePath !== null ? 'storage' : 'default',
            'filename' => $absolute && is_file($absolute) ? basename($absolute) : null,
            'updated_at' => $absolute && is_file($absolute) ? filemtime($absolute) : null,
            'size_kb' => $absolute && is_file($absolute) ? round(filesize($absolute) / 1024, 1) : null,
            'storage_writable' => $this->storageIsWritable(),
        ];
    }

    public function uploadAppLogo(UploadedFile $file): string
    {
        $extension = $this->normalizeExtension($file);
        $this->deleteAppLogoFiles();

        $directory = trim((string) config('branding.app.storage_directory', 'branding'), '/');
        $basename = (string) config('branding.app.basename', 'app-logo');
        $filename = "{$basename}.{$extension}";
        $relative = "{$directory}/{$filename}";

        File::ensureDirectoryExists(Storage::disk('public')->path($directory));

        if (!is_writable(Storage::disk('public')->path($directory))) {
            throw new \RuntimeException('storage/app/public/branding is not writable. Set folder permissions to 775 on the server.');
        }

        $stored = Storage::disk('public')->putFileAs($directory, $file, $filename);
        if ($stored === false || !Storage::disk('public')->exists($relative)) {
            throw new \RuntimeException('Logo upload failed. The file could not be saved on the server.');
        }

        $absolute = Storage::disk('public')->path($relative);
        $this->mirrorAppLogoToPublic($absolute);
        $this->tryEnsureStorageSymlink();
        $this->flushCache();

        return $relative;
    }

    public function removeAppLogo(): bool
    {
        $removed = $this->deleteAppLogoFiles();
        $this->flushCache();

        return $removed;
    }

    // ── Company (tenant) logo ─────────────────────────────────────────────

    public function resolveCompany(Company $company, string $context = 'sidebar'): array
    {
        $config = config("branding.company.contexts.{$context}")
            ?? config('branding.company.contexts.sidebar', []);
        $class = $config['class'] ?? 'logo logo--company';
        $fallback = $config['fallback'] ?? 'none';

        $asset = $this->companyLogoAssetOnly($company);
        if ($asset !== null) {
            return [
                'url' => $asset['url'],
                'fallback_url' => null,
                'alt' => $company->name,
                'class' => $class,
                'has_logo' => true,
                'source' => 'company',
            ];
        }

        if ($fallback === 'app') {
            $app = $this->resolveApp('topbar');

            return [
                'url' => $app['url'],
                'fallback_url' => $app['fallback_url'],
                'alt' => $company->name,
                'class' => $class,
                'has_logo' => false,
                'source' => 'app-fallback',
            ];
        }

        return [
            'url' => null,
            'fallback_url' => null,
            'alt' => $company->name,
            'class' => $class,
            'has_logo' => false,
            'source' => 'none',
        ];
    }

    public function companyLogoUrl(Company $company): ?string
    {
        return $this->companyLogoAssetOnly($company)['url'] ?? null;
    }

    public function companyLogoAssetOnly(Company $company): ?array
    {
        $absolute = $this->companyLogoAbsolutePath($company->id);
        if ($absolute === null) {
            return null;
        }

        return [
            'url' => route('brand.company-logo', [
                'company' => $company->id,
                'v' => $this->versionToken($absolute),
            ]),
            'path' => $absolute,
        ];
    }

    public function uploadCompanyLogo(Company $company, UploadedFile $file): string
    {
        if ($company->logo) {
            Storage::disk('public')->delete($company->logo);
            $this->deleteCompanyLogoMirror($company);
        }

        $directory = "logos/{$company->id}";
        File::ensureDirectoryExists(Storage::disk('public')->path($directory));

        $path = $file->store($directory, 'public');
        if (!$path || !Storage::disk('public')->exists($path)) {
            throw new \RuntimeException('Company logo could not be saved. Check storage/app/public permissions.');
        }

        $company->update(['logo' => $path]);
        $this->mirrorCompanyLogoToPublic($company, Storage::disk('public')->path($path));

        return $path;
    }

    public function removeCompanyLogoMirror(Company $company): void
    {
        $this->deleteCompanyLogoMirror($company);
    }

    public function localCompanyLogoPath(Company $company): ?string
    {
        return $this->companyLogoAbsolutePath($company->id);
    }

    public function flushCache(): void
    {
        $this->appLogoCache = null;
    }

    // ── Internal helpers ────────────────────────────────────────────────────

    private function findAppLogoStoragePath(): ?string
    {
        $directory = trim((string) config('branding.app.storage_directory', 'branding'), '/');
        $basename = (string) config('branding.app.basename', 'app-logo');

        return $this->findFileByBasename(Storage::disk('public'), $directory, $basename);
    }

    private function findAppLogoPublicMirror(): ?string
    {
        $basename = (string) config('branding.app.basename', 'app-logo');

        return $this->findFileByBasenameOnDisk(public_path('images'), $basename);
    }

    private function findCompanyLogoPublicMirror(Company $company): ?string
    {
        $directory = public_path(trim((string) config('branding.company.public_mirror_directory', 'company-logos'), '/'));

        return $this->findFileByBasenameOnDisk($directory, (string) $company->id);
    }

    private function findFileByBasename($disk, string $directory, string $basename): ?string
    {
        foreach ($this->allowedExtensions() as $extension) {
            $relative = "{$directory}/{$basename}.{$extension}";
            if ($disk->exists($relative)) {
                return $relative;
            }
        }

        return null;
    }

    private function findFileByBasenameOnDisk(string $directory, string $basename): ?string
    {
        foreach ($this->allowedExtensions() as $extension) {
            $absolute = "{$directory}/{$basename}.{$extension}";
            if (is_file($absolute)) {
                return $absolute;
            }
        }

        return null;
    }

    private function assetFromPublicPath(string $absolute, string $urlPrefix): array
    {
        $relative = trim($urlPrefix, '/') . '/' . basename($absolute);

        return [
            'url' => asset($relative) . $this->versionQuery($absolute),
            'path' => $absolute,
        ];
    }

    private function assetFromStoragePath(string $relativePath): array
    {
        $absolute = Storage::disk('public')->path($relativePath);

        return [
            'url' => asset('storage/' . $relativePath) . $this->versionQuery($absolute),
            'path' => $absolute,
        ];
    }

    private function syncAppLogoStorageToPublicMirror(): void
    {
        $storagePath = $this->findAppLogoStoragePath();
        if ($storagePath === null) {
            return;
        }

        $source = Storage::disk('public')->path($storagePath);
        if (!is_file($source)) {
            return;
        }

        $basename = (string) config('branding.app.basename', 'app-logo');
        $extension = strtolower(pathinfo($source, PATHINFO_EXTENSION));
        $dest = public_path("images/{$basename}.{$extension}");

        if (!is_file($dest) || filemtime($source) > filemtime($dest)) {
            $this->mirrorAppLogoToPublic($source);
        }
    }

    private function syncCompanyLogoToPublicMirror(Company $company): void
    {
        if (!$company->hasLogo()) {
            return;
        }

        $source = Storage::disk('public')->path((string) $company->logo);
        if (!is_file($source)) {
            return;
        }

        $mirror = $this->findCompanyLogoPublicMirror($company);
        if ($mirror === null || filemtime($source) > filemtime($mirror)) {
            $this->mirrorCompanyLogoToPublic($company, $source);
        }
    }

    private function mirrorAppLogoToPublic(string $sourcePath): void
    {
        if (!is_file($sourcePath)) {
            return;
        }

        $basename = (string) config('branding.app.basename', 'app-logo');
        $extension = strtolower(pathinfo($sourcePath, PATHINFO_EXTENSION));

        File::ensureDirectoryExists(public_path('images'));
        $this->deleteBasenameFiles(public_path('images'), $basename);
        copy($sourcePath, public_path("images/{$basename}.{$extension}"));
    }

    private function mirrorCompanyLogoToPublic(Company $company, string $sourcePath): void
    {
        if (!is_file($sourcePath)) {
            return;
        }

        $directory = public_path(trim((string) config('branding.company.public_mirror_directory', 'company-logos'), '/'));
        $extension = strtolower(pathinfo($sourcePath, PATHINFO_EXTENSION));

        File::ensureDirectoryExists($directory);
        $this->deleteBasenameFiles($directory, (string) $company->id);
        copy($sourcePath, "{$directory}/{$company->id}.{$extension}");
    }

    private function deleteAppLogoFiles(): bool
    {
        $removed = false;
        $directory = trim((string) config('branding.app.storage_directory', 'branding'), '/');
        $basename = (string) config('branding.app.basename', 'app-logo');

        foreach ($this->allowedExtensions() as $extension) {
            $relative = "{$directory}/{$basename}.{$extension}";
            if (Storage::disk('public')->exists($relative)) {
                Storage::disk('public')->delete($relative);
                $removed = true;
            }

            $public = public_path("images/{$basename}.{$extension}");
            if (is_file($public)) {
                @unlink($public);
                $removed = true;
            }
        }

        return $removed;
    }

    private function deleteCompanyLogoMirror(Company $company): void
    {
        $directory = public_path(trim((string) config('branding.company.public_mirror_directory', 'company-logos'), '/'));
        $this->deleteBasenameFiles($directory, (string) $company->id);
    }

    private function deleteBasenameFiles(string $directory, string $basename): void
    {
        foreach ($this->allowedExtensions() as $extension) {
            $file = "{$directory}/{$basename}.{$extension}";
            if (is_file($file)) {
                @unlink($file);
            }
        }
    }

    private function isStorageWebAccessible(string $relativePath): bool
    {
        return is_file(public_path('storage/' . $relativePath));
    }

    private function tryEnsureStorageSymlink(): void
    {
        $link = public_path('storage');
        if (is_link($link) || is_dir($link)) {
            return;
        }

        try {
            \Illuminate\Support\Facades\Artisan::call('storage:link');
        } catch (\Throwable) {
            //
        }
    }

    private function normalizeExtension(UploadedFile $file): string
    {
        $extension = strtolower($file->getClientOriginalExtension() ?: $file->extension() ?: 'png');

        return in_array($extension, $this->allowedExtensions(), true) ? $extension : 'png';
    }

    private function allowedExtensions(): array
    {
        return ['png', 'jpg', 'jpeg', 'svg', 'webp'];
    }

    private function versionQuery(string $absolutePath): string
    {
        if (!is_file($absolutePath)) {
            return '';
        }

        return '?v=' . $this->versionToken($absolutePath);
    }

    private function versionToken(string $absolutePath): string
    {
        if (!is_file($absolutePath)) {
            return '0';
        }

        return filemtime($absolutePath) . '-' . filesize($absolutePath);
    }
}
