<?php

namespace Modules\SmartStorage\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Schema;
use League\Flysystem\Filesystem as Flysystem;
use Illuminate\Filesystem\FilesystemAdapter as IlluminateFilesystemAdapter;
use Modules\SmartStorage\Entities\SsSetting;
use Modules\SmartStorage\Entities\Services\StorageDriverRegistry;
use Modules\SmartStorage\Entities\Drivers\LaravelDefaultBackendDriver;
use Modules\SmartStorage\Entities\Drivers\LocalBackendDriver;
use Modules\SmartStorage\Entities\Drivers\S3BackendDriver;
use Modules\SmartStorage\Entities\Drivers\DropboxBackendDriver;
use Modules\SmartStorage\Entities\Drivers\GoogleDriveBackendDriver;
use Modules\SmartStorage\Entities\Flysystem\SmartStorageAdapter;
use Modules\SmartStorage\Entities\Services\FileEncryptionService;
use Modules\SmartStorage\Entities\Services\AccessLogger;

class SmartStorageServiceProvider extends ServiceProvider
{
    /**
     * Boot the application events.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerConfig();
        $this->registerViews();
        $this->loadMigrationsFrom(__DIR__ . '/../Database/Migrations');
        $this->registerTranslations();
        $this->registerCommands();

        $this->registerBackendDrivers();
        $this->registerSmartDisk();
        $this->maybeSwapTargetDisk();
    }

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->register(RouteServiceProvider::class);
    }

    /**
     * Register the built-in backend drivers with the registry. Drivers degrade
     * gracefully — a driver whose SDK/package is missing reports isAvailable()
     * = false and simply cannot be selected.
     */
    protected function registerBackendDrivers(): void
    {
        StorageDriverRegistry::register(new LaravelDefaultBackendDriver());
        StorageDriverRegistry::register(new LocalBackendDriver());
        StorageDriverRegistry::register(new S3BackendDriver());
        StorageDriverRegistry::register(new DropboxBackendDriver());
        StorageDriverRegistry::register(new GoogleDriveBackendDriver());
    }

    /**
     * Register the `smart-storage` Flysystem driver so it can back any Laravel
     * disk. The driver builds the configured backend adapter and wraps it in the
     * SmartStorageAdapter decorator (encryption + indexing + access logging).
     */
    protected function registerSmartDisk(): void
    {
        Storage::extend(config('smartstorage.driver', 'smart-storage'), function ($app, $config) {
            $settings  = SsSetting::current();
            $backend   = StorageDriverRegistry::get($settings->active_backend);

            // Defensive: if the configured backend is gone/unavailable, fall back
            // to a plain local adapter rooted at the legacy public path so the app
            // keeps working rather than throwing on every request.
            if (!$backend || !$backend->isAvailable()) {
                $backend = StorageDriverRegistry::get('laravel-default');
            }

            $innerAdapter = $backend->makeAdapter($settings);

            $smart = new SmartStorageAdapter(
                $innerAdapter,
                $settings,
                app(FileEncryptionService::class),
                app(AccessLogger::class)
            );

            return new IlluminateFilesystemAdapter(
                new Flysystem($smart, $config),
                $smart,
                $config
            );
        });
    }

    /**
     * The crux of "no main-code changes": when the module is installed and
     * enabled with a non-default backend, rebind the disk that all existing
     * upload code resolves so every store()/storeAs() flows through us.
     */
    protected function maybeSwapTargetDisk(): void
    {
        // Never interfere while migrating/installing or before the table exists.
        if (!$this->tableReady()) {
            return;
        }

        try {
            $settings = SsSetting::current();
        } catch (\Throwable $e) {
            return;
        }

        // laravel-default == leave the framework exactly as it is.
        if ($settings->active_backend === 'laravel-default') {
            return;
        }

        $target = config('smartstorage.target_disk', 'local');

        $existing = config("filesystems.disks.{$target}", []);

        config([
            "filesystems.disks.{$target}" => array_merge($existing, [
                'driver' => config('smartstorage.driver', 'smart-storage'),
                // Preserve a URL hint so Storage::url() keeps producing the
                // legacy /uploads/... shape that views already use.
                'url'    => $existing['url'] ?? (url('/') . '/' . trim(config('smartstorage.public_prefix', 'uploads'), '/')),
            ]),
        ]);

        // Drop any already-resolved instance so the new config takes effect.
        Storage::forgetDisk($target);
    }

    protected function tableReady(): bool
    {
        try {
            return Schema::hasTable('ss_settings');
        } catch (\Throwable $e) {
            return false;
        }
    }

    protected function registerCommands(): void
    {
        if ($this->app->runningInConsole()) {
            $this->commands([
                \Modules\SmartStorage\Console\ReconcileCommand::class,
            ]);
        }
    }

    /**
     * Register config.
     */
    protected function registerConfig()
    {
        $this->publishes([
            __DIR__ . '/../Config/config.php' => config_path('smartstorage.php'),
        ], 'config');
        $this->mergeConfigFrom(
            __DIR__ . '/../Config/config.php',
            'smartstorage'
        );
    }

    /**
     * Register views.
     */
    public function registerViews()
    {
        $viewPath = resource_path('views/modules/SmartStorage');

        $sourcePath = __DIR__ . '/../Resources/views';

        $this->publishes([
            $sourcePath => $viewPath,
        ], 'views');

        $this->loadViewsFrom(array_merge(array_map(function ($path) {
            return $path . '/modules/smartstorage';
        }, \Config::get('view.paths')), [$sourcePath]), 'smartstorage');
    }

    /**
     * Register translations.
     */
    public function registerTranslations()
    {
        $langPath = resource_path('lang/modules/SmartStorage');

        if (is_dir($langPath)) {
            $this->loadTranslationsFrom($langPath, 'smartstorage');
        } else {
            $this->loadTranslationsFrom(__DIR__ . '/../Resources/lang', 'smartstorage');
        }
    }

    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides()
    {
        return [];
    }
}
