<?php
namespace FlysystemOffload;

use FlysystemOffload\Config\ConfigLoader;
use FlysystemOffload\Filesystem\FilesystemFactory;
use FlysystemOffload\Helpers\PathHelper;
use FlysystemOffload\Media\MediaHooks;
use FlysystemOffload\StreamWrapper\FlysystemStreamWrapper;
use League\Flysystem\FilesystemOperator;

class Plugin
{
    private static $instance;
    private static string $pluginFile;

    private ?FilesystemOperator $filesystem = null;
    private bool $streamRegistered = false;
    private array $config = [];
    private ConfigLoader $configLoader;
    private MediaHooks $mediaHooks;

    public function __construct()
    {
        $this->mediaHooks = new MediaHooks();
    }

    public static function bootstrap(string $pluginFile): void
    {
        self::$pluginFile = $pluginFile;

        register_activation_hook($pluginFile, [self::class, 'activate']);
        register_deactivation_hook($pluginFile, [self::class, 'deactivate']);

        add_action('plugins_loaded', static function () {
            self::instance()->init();
        });
    }

    public static function instance(): self
    {
        return self::$instance ??= new self();
    }

    public static function activate(): void
    {
        wp_mkdir_p(WP_CONTENT_DIR . '/flysystem-uploads');

        if (! defined('FLYSYSTEM_OFFLOAD_CONFIG') && ! file_exists(WP_CONTENT_DIR . '/flysystem-offload.php')) {
            error_log('[Flysystem Offload] No se encontró un archivo de configuración. Copia config/flysystem-offload.example.php a wp-content/flysystem-offload.php y ajústalo.');
        }
    }

    public static function deactivate(): void
    {
        if ($instance = self::$instance) {
            $instance->mediaHooks->unregister();
            $instance->mediaHooks->setFilesystem(null);
        }

        if (in_array('fly', stream_get_wrappers(), true)) {
            stream_wrapper_unregister('fly');
        }
    }

    public function init(): void
    {
        $this->configLoader = new ConfigLoader(self::$pluginFile);

        $this->reloadConfig();

        add_filter('upload_dir', [$this, 'filterUploadDir'], 20);
        add_filter('wp_get_attachment_url', [$this, 'filterAttachmentUrl'], 20, 2);
        add_filter('wp_get_attachment_metadata', [$this, 'filterAttachmentMetadata'], 20);
        add_filter('wp_get_original_image_path', [$this, 'filterOriginalImagePath'], 20);
        add_filter('wp_delete_file', [$this, 'handleDeleteFile'], 20);

        add_action('delete_attachment', [$this, 'handleDeleteAttachment'], 20);
        add_action('switch_blog', [$this, 'handleSwitchBlog']);

        add_action('flysystem_offload_reload_config', [$this, 'reloadConfig']);

        if (defined('WP_CLI') && WP_CLI) {
            \WP_CLI::add_command('flysystem-offload health-check', [Admin\HealthCheck::class, 'run']);
        }
    }

    public function reloadConfig(): void
    {
        try {
            $this->config = $this->configLoader->load();
        } catch (\Throwable $e) {
            error_log('[Flysystem Offload] Error cargando configuración: ' . $e->getMessage());
            $this->config = $this->configLoader->defaults();
        }

        $this->filesystem = null;
        $this->streamRegistered = false;

        $this->mediaHooks->unregister();
        $this->mediaHooks->setFilesystem(null);

        $this->registerStreamWrapper();
    }

    public function handleSwitchBlog(): void
    {
        $this->reloadConfig();
    }

    public function getFilesystem(): FilesystemOperator
    {
        if (! $this->filesystem) {
            $factory = new FilesystemFactory($this->config);
            $result  = $factory->make();

            if (is_wp_error($result)) {
                throw new \RuntimeException($result->get_error_message());
            }

            $this->filesystem = $result;
        }

        return $this->filesystem;
    }

    private function registerStreamWrapper(): void
    {
        if ($this->streamRegistered) {
            return;
        }

        try {
            $filesystem = $this->getFilesystem();
        } catch (\Throwable $e) {
            error_log('[Flysystem Offload] No se pudo obtener el filesystem: ' . $e->getMessage());
            return;
        }

        try {
            FlysystemStreamWrapper::register(
                $filesystem,
                'fly',
                PathHelper::normalizePrefix($this->config['base_prefix'] ?? '')
            );
            $this->streamRegistered = true;
        } catch (\Throwable $e) {
            error_log('[Flysystem Offload] No se pudo registrar el stream wrapper: ' . $e->getMessage());
        }

        $this->mediaHooks->setFilesystem($filesystem);
        $this->mediaHooks->register();
    }

    public function filterUploadDir(array $dirs): array
    {
        $remoteBase = $this->getRemoteUrlBase();
        $prefix     = PathHelper::normalizePrefix($this->config['base_prefix'] ?? '');
        $subdir     = $dirs['subdir'] ?? '';

        $dirs['path']    = "fly://{$prefix}{$subdir}";
        $dirs['basedir'] = "fly://{$prefix}";
        $dirs['url']     = trailingslashit($remoteBase) . ltrim($subdir, '/');
        $dirs['baseurl'] = $remoteBase;

        return $dirs;
    }

    public function filterAttachmentUrl(string $url, int $postId): string
    {
        $localBase  = trailingslashit(wp_get_upload_dir()['baseurl']);
        $remoteBase = trailingslashit($this->getRemoteUrlBase());

        return str_replace($localBase, $remoteBase, $url);
    }

    public function filterAttachmentMetadata(array $metadata): array
    {
        if (! empty($metadata['file'])) {
            $metadata['file'] = PathHelper::stripProtocol($metadata['file']);
        }

        if (! empty($metadata['sizes'])) {
            foreach ($metadata['sizes'] as &$size) {
                if (! empty($size['file'])) {
                    $size['file'] = ltrim($size['file'], '/');
                }
            }
            unset($size);
        }

        return $metadata;
    }

    public function filterOriginalImagePath(string $path): string
    {
        return PathHelper::ensureFlyProtocol($path);
    }

    public function handleDeleteFile(string $file): string|false
    {
        $flyPath = PathHelper::stripProtocol($file);

        try {
            $this->getFilesystem()->delete($flyPath);
        } catch (\Throwable $e) {
            error_log('[Flysystem Offload] Error al borrar archivo: ' . $flyPath . ' - ' . $e->getMessage());
        }

        return false;
    }

    public function handleDeleteAttachment(int $postId): void
    {
        $files = PathHelper::collectFilesFromAttachment($postId);

        foreach ($files as $relativePath) {
            try {
                $this->getFilesystem()->delete($relativePath);
            } catch (\Throwable $e) {
                error_log('[Flysystem Offload] Error al borrar attachment: ' . $relativePath . ' - ' . $e->getMessage());
            }
        }
    }

    private function getRemoteUrlBase(): string
    {
        $adapterKey = $this->config['adapter'] ?? 'local';
        $settings   = $this->config['adapters'][$adapterKey] ?? [];

        return (new FilesystemFactory($this->config))->resolvePublicBaseUrl($adapterKey, $settings);
    }
}
