<?php
/**
 * BookiFlex Core
 *
 * @package    BookiFlex
 * @subpackage Core
 * @author     BookiFlex
 * @copyright  Copyright (c) 2025 BookiFlex
 * @license    GPL-2.0-or-later
 *
 * @link       https://bookiflex.com
 */

declare(strict_types=1);

namespace BookiFlex\Core;

use BookiFlex\Core\Admin\Lifecycle\ActivationOrchestrator;
use BookiFlex\Core\Admin\Settings\SettingsFieldsConfig;
use BookiFlex\Core\Admin\Settings\SettingsManager;
use BookiFlex\Core\Container\ServiceContainer;
use BookiFlex\Core\Container\ServiceProvider;
use BookiFlex\Core\Contract\PluginInterface;
use BookiFlex\Core\ORM\SchemaManager;
use BookiFlex\Core\Provider\DBConfig;
use BookiFlex\Core\Service\AccommodationType\AccommodationCapacityFinder;
use BookiFlex\Core\Trait\ExtensionsTrait;
use BookiFlex\Core\Trait\HooksTrait;
use BookiFlex\Core\Util\Twig;
use Carbon_Fields\Carbon_Fields;
use Cycle\Database\Config;
use Cycle\Database\DatabaseManager;
use Cycle\ORM\Factory;
use Cycle\ORM\ORM;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Twig\Cache\FilesystemCache;
use Twig\Environment as TwigEnvironment;
use Twig\Loader\FilesystemLoader;
use Twig\TwigFunction;

defined('ABSPATH') || exit;

/**
 * Abstract base plugin class for BookiFlex
 *
 * @package BookiFlex\Core
 */
abstract class AbstractPlugin implements PluginInterface
{
    use HooksTrait;
    use ExtensionsTrait;

    protected ServiceContainer $container;
    protected string $pluginFile;
    protected string $pluginDir;
    protected string $pluginUrl;
    protected string $version;
    protected string $textDomain = 'bookiflex';

    // Lifecycle handlers
    protected ?ActivationOrchestrator $activationOrchestrator = null;
    protected ?Admin\Lifecycle\AbstractUninstaller $uninstaller = null;
    protected ?Admin\AbstractAssetsManager $assetsManager = null;

    /**
     * Plugin configurations that can be extended by implementations
     */
    protected array $configs = [
        'controllers' => [],
        'settings'    => [],
        'menus'       => [],
        'schemas'     => [],
        'cpt'         => [],
        'blocks'      => [],
        'subscribers' => [],
    ];

    /**
     * Core components available in all versions
     */
    protected array $coreComponents = [
        'cpt' => [],
        'controllers' => [
            Api\Controller\PricingController::class,
            Api\Controller\OfferController::class,
            Api\Controller\DateUnitController::class,
            Api\Controller\AccountController::class,
            Api\Controller\AccommodationTypeController::class,
            Api\Controller\RatePlanController::class,
            Api\Controller\ReservationController::class,
        ],
        'menus' => [
            Admin\Menu\MainMenu::class,
            Admin\Menu\ReservationMenu::class,
            Admin\Menu\CustomerMenu::class,
        ],
        'blocks' => [],
        'subscribers' => [],
    ];

    public function __construct(string $pluginFile, string $version)
    {
        $this->pluginFile = $pluginFile;
        $this->pluginDir = plugin_dir_path($pluginFile);
        $this->pluginUrl = plugins_url('', $pluginFile);
        $this->version = $version;

        $this->_initializeContainer();
        $this->_registerCoreServices();

        // Register lifecycle
        $this->_initializeLifecycle();

        // Register assets
        $this->_registerAssets();
    }

    /**
     * Initialize the plugin
     */
    public function init(): void
    {
        // Limit Action Scheduler history to 3 days (default is 30)
        $this->addFilter('action_scheduler_retention_period', fn() => 3 * DAY_IN_SECONDS);

        // Register WordPress hooks
        $this->_registerHooks();

        // Initialize components
        $this->addAction('init', [$this, 'onWordPressInit'], 0);
        $this->addAction('after_setup_theme', [$this, 'onAfterSetupTheme']);

        // Debug-only: log peak/current memory usage for every bflex REST request.
        // Uncomment in dev. The Debug/ directory is removed in the production build,
        // so the class is referenced by FQN to keep `use`-imports clean.
        // (new \BookiFlex\Core\Debug\RequestMemoryLogger($this->getLogger()))->register();
    }

    /**
     * Called when WordPress is initialized
     */
    public function onWordPressInit(): void
    {
        load_plugin_textdomain('bookiflex', false, basename($this->pluginDir) . '/languages');

        $this->_initializeSession();
        $this->_registerPersonalDataHandlers();

        do_action('bookiflex_init', $this);
        do_action('bookiflex_' . $this->getEdition() . '_init', $this);
    }

    /**
     * Called after theme is setup
     */
    public function onAfterSetupTheme(): void
    {
        add_image_size('bflex-card', 860, 574, true);
        add_image_size('bflex-card-lg', 1300, 867, true);
        add_image_size('bflex-gallery', 1920, 1280, false);

        $this->bootCarbonFields();

        $this->loadExtensions();
        $this->_initializeORM();
        $this->_registerComponents();

        do_action('bookiflex_initialized', $this);
        do_action('bookiflex_' . $this->getEdition() . '_initialized', $this);
    }

    /**
     * Boot Carbon Fields with a compatibility guard.
     *
     * CF::boot() is idempotent (has its own is_booted() check), so calling it
     * when another plugin already booted CF is safe. However, if a third-party
     * plugin loaded an older CF version (before our autoloader ran), it may lack
     * the API we depend on — in that case we show an admin notice instead of
     * crashing silently mid-boot.
     */
    private function bootCarbonFields(): void
    {
        // Another plugin loaded CF before our autoloader registered its PSR-4 paths.
        // Verify the loaded version has the API we need before booting.
        if (defined('BOOKIFLEX_CARBON_FIELDS_PRELOADED') && BOOKIFLEX_CARBON_FIELDS_PRELOADED) {
            if (!method_exists('Carbon_Fields\\Carbon_Fields', 'boot')
                || !method_exists('Carbon_Fields\\Carbon_Fields', 'is_booted')
            ) {
                add_action('admin_notices', function () {
                    printf(
                        '<div class="notice notice-error is-dismissible"><p>%s</p></div>',
                        esc_html__(
                            'BookiFlex: An incompatible Carbon Fields version was loaded by another plugin. Settings fields may not work correctly. Please deactivate conflicting plugins.',
                            'bookiflex'
                        )
                    );
                });
                return;
            }
        }

        Carbon_Fields::boot();
    }

    /**
     * Get plugin edition (free/pro)
     * Must be implemented by child classes
     */
    abstract public function getEdition(): string;

    /**
     * Register version-specific services
     * Must be implemented by child classes
     */
    abstract protected function registerServices(): void;

    /**
     * Get the service container
     */
    public function getContainer(): ServiceContainer
    {
        return $this->container;
    }

    /**
     * Get event dispatcher
     */
    public function getEventDispatcher(): EventDispatcher
    {
        return $this->container['event_dispatcher'];
    }

    /**
     * Get settings manager
     */
    public function getSettingsManager(): SettingsManager
    {
        return $this->container->get('settings_manager');
    }

    /**
     * Add plugin configuration
     */
    public function addConfig(array $config): void
    {
        foreach ($config as $key => $values) {
            if (isset($this->configs[$key]) && is_array($this->configs[$key]) && is_array($values)) {
                $this->configs[$key] = array_merge($this->configs[$key], $values);
            }
        }
    }

    /**
     * Initialize service container
     */
    private function _initializeContainer(): void
    {
        $this->container = new ServiceContainer();

        // Register plugin instance
        $this->container['plugin'] = $this;
        $this->container[\BookiFlex\Core\Contract\PluginInterface::class] = fn($c) => $c['plugin'];
    }

    /**
     * Register core services in container
     */
    private function _registerCoreServices(): void
    {
        $this->container['version'] = $this->version;

        // Return plugin's file
        $this->container['plugin_file'] = $this->pluginFile;

        // Return directory of plugin
        $this->container['plugin_dir'] = $this->pluginDir;

        // Return URL of plugin
        $this->container['plugin_url'] = $this->pluginUrl;

        // Event Dispatcher
        $this->container['event_dispatcher'] = function () {
            return new EventDispatcher();
        };

        // Database Manager
        $this->container['db_manager'] = function () {
            return $this->_createDatabaseManager();
        };

        // ORM will be registered after initialization
        $this->container[ORM::class] = function ($c) {
            return $c['_orm_instance'] ?? null;
        };

        // Schema Manager for migrations
        $this->container[SchemaManager::class] = function ($c) {
            return new SchemaManager($c['db_manager'], $c->get(LoggerInterface::class));
        };
        // Alias for backward compatibility
        $this->container['schema_manager'] = function ($c) {
            return $c->get(SchemaManager::class);
        };

        // Configuration Service
        $this->container['config'] = function ($c) {
            return new Service\ConfigurationService($c->get(AccommodationCapacityFinder::class));
        };

        // Session Service
        $this->container[Service\SessionService::class] = function () {
            return new Service\SessionService();
        };

        // Template engine
        $this->container['twig'] = function () {
            return $this->_createTwigEnvironment();
        };

        // Settings manager
        $this->container['settings_manager'] = function () {
            return new SettingsManager(
                new SettingsFieldsConfig(),
                $this
            );
        };

        // Logger - using WPLogger for WordPress debug.log
        $this->container[LoggerInterface::class] = function () {
            $minLevel = (defined('WP_DEBUG') && WP_DEBUG) ? 'debug' : 'warning';
            return new \BookiFlex\Core\Infrastructure\Logger\WPLogger('bookiflex', $minLevel);
        };

        // Load other common services
        $this->container->register(new ServiceProvider());

        // Let child classes register their services
        $this->registerServices();

    }

    /**
     * Create database manager instance
     */
    private function _createDatabaseManager(): DatabaseManager
    {
        $config = new Config\DatabaseConfig([
            'default'   => 'default',
            'databases' => [
                'default' => ['connection' => 'mysql']
            ],
            'connections' => [
                'mysql' => new Config\MySQLDriverConfig(
                    connection: new Config\MySQL\TcpConnectionConfig(
                        database: DBConfig::getName(),
                        host: DBConfig::getHost(),
                        port: DBConfig::getPort(),
                        user: DBConfig::getUser(),
                        password: DBConfig::getPassword(),
                    ),
                    queryCache: true
                ),
            ]
        ]);

        return new DatabaseManager($config);
    }

    private function _createTwigEnvironment(): TwigEnvironment
    {
        $loader = new FilesystemLoader();
        $twig = new TwigEnvironment($loader);

        // Configure cache
        if (static::isDev()) {
            $twig->enableDebug();
        } else {
            $twig->setCache(
                new FilesystemCache(
                    get_temp_dir() . 'bflex-twig-cache'
                )
            );
        }

        // Reservation status helper
        $twig->addFunction(new TwigFunction('reservation_status_label', function ($status) {
            return Twig::getReservationStatusLabel($status);
        }));

        return $twig;
    }

    /**
     * Initialize ORM
     */
    private function _initializeORM(): void
    {
        $schemas = $this->_collectSchemas();

        $orm = new ORM(
            factory: new Factory($this->container['db_manager']),
            schema: new \Cycle\ORM\Schema($schemas)
        );

        $this->container['_orm_instance'] = $orm;
    }

    /**
     * Collect all registered schemas
     */
    private function _collectSchemas(): array
    {
        $schemas = [];

        foreach ($this->configs['schemas'] as $schemaClass) {
            if (method_exists($schemaClass, 'getSchema')) {
                $schemas = array_merge($schemas, $schemaClass::getSchema());
            }
        }

        return $schemas;
    }

    /**
     * Register all components (CPT, Controllers, Menus)
     */
    private function _registerComponents(): void
    {
        $this->_registerMenus();
        $this->_registerCustomPostTypes();
        $this->_registerControllers();
        $this->_registerSettings();
        $this->_registerBlockCategory();
        $this->_registerGutenbergBlocks();
        $this->_registerEventSubscribers();
    }

    private function _registerBlockCategory(): void
    {
        add_filter('block_categories_all', function (array $categories): array {
            return array_merge($categories, [[
                'slug'  => 'bookiflex',
                'title' => 'BookiFlex',
                'icon'  => 'calendar',
            ]]);
        });
    }

    /**
     * Register custom post types
     */
    private function _registerCustomPostTypes(): void
    {
        $allCpts = array_merge($this->coreComponents['cpt'], $this->configs['cpt']);

        foreach ($allCpts as $cptClass) {
            /** @var Admin\CPT\AbstractCPT $cpt */
            $cpt = $this->container->make($cptClass);
            $cpt->register($this);
        }
    }

    /**
     * Register REST API controllers
     */
    private function _registerControllers(): void
    {
        $this->addAction('rest_api_init', [$this, 'registerRestControllers']);
    }

    /**
     * Register REST API routes for all controllers
     */
    public function registerRestControllers(): void
    {
        $allControllers = array_merge($this->coreComponents['controllers'], $this->configs['controllers']);

        foreach ($allControllers as $controllerClass) {
            $controller = $this->container->get($controllerClass);

            if ($controller instanceof Api\Controller\AbstractController) {
                $controller->registerRoutes();
            }
        }
    }

    /**
     * Register admin menus
     */
    private function _registerMenus(): void
    {
        if (!is_admin()) {
            return;
        }

        $allMenus = array_merge($this->coreComponents['menus'], $this->configs['menus']);

        foreach ($allMenus as $menuClass) {
            /** @var Admin\Menu\AbstractMenu $menu */
            $menu = $this->container->make($menuClass);
            $menu->register($this);
        }
    }

    /**
     * Register settings
     */
    private function _registerSettings(): void
    {
        if (!empty($this->configs['settings'])) {
            $this->getSettingsManager()->addSettings($this->configs['settings']);
        }
        $this->getSettingsManager()->register();
    }

    /**
     * Register Gutenberg blocks
     */
    private function _registerGutenbergBlocks(): void
    {
        $allBlocks = array_merge($this->coreComponents['blocks'], $this->configs['blocks']);

        foreach ($allBlocks as $blockClass) {
            /** @var Admin\Block\AbstractBlock $block */
            $block = $this->container->make($blockClass);
            $block->register();
        }
    }

    /**
     * Register event subscribers
     */
    private function _registerEventSubscribers(): void
    {
        $subscribers = array_merge($this->coreComponents['subscribers'], $this->configs['subscribers']);

        foreach ($subscribers as $subscriber) {
            $this->container->get('event_dispatcher')->addSubscriber(
                $this->container->get($subscriber)
            );
        }
    }

    /**
     * Initialize session handling
     */
    private function _initializeSession(): void
    {
        if (!is_admin()) {
            $this->container[Service\SessionService::class]->init();
        }
    }

    /**
     * Register personal data handlers for GDPR
     */
    private function _registerPersonalDataHandlers(): void
    {
        new Admin\PersonalData\DataExporter($this);
        new Admin\PersonalData\DataEraser($this);
    }

    /**
     * Register WordPress hooks
     */
    private function _registerHooks(): void
    {
        // Admin notices
        $this->addAction('admin_notices', [$this, 'displayAdminNotices']);
    }

    // =========================================================================
    // LIFECYCLE MANAGEMENT
    // =========================================================================

    /**
     * Initialize lifecycle components
     */
    private function _initializeLifecycle(): void
    {
        // Create and register activation orchestrator
        $this->activationOrchestrator = $this->createActivationOrchestrator();
        $this->activationOrchestrator->register();

        // Create uninstaller
        $this->uninstaller = $this->createUninstaller();

        // Register uninstaller
        if ($this->uninstaller) {
            $this->uninstaller::register($this);
        }

        // Setup postponed data after Carbon Fields loaded
        $this->addAction('carbon_fields_fields_registered', [$this, 'setupPostponedData']);
    }

    /**
     * Create and configure activation orchestrator
     *
     * Must be implemented by child classes to configure
     * extension activators and other version-specific settings.
     */
    abstract protected function createActivationOrchestrator(): ActivationOrchestrator;

    /**
     * Create version-specific uninstaller
     */
    abstract protected function createUninstaller(): Admin\Lifecycle\AbstractUninstaller;

    /**
     * Get activation orchestrator
     */
    public function getActivationOrchestrator(): ActivationOrchestrator
    {
        return $this->activationOrchestrator;
    }

    /**
     * Setup postponed data after Carbon Fields is loaded
     */
    public function setupPostponedData(): void
    {
        $this->activationOrchestrator?->runPostponedSetupIfNeeded();
    }

    // =========================================================================
    // UTILITIES
    // =========================================================================

    /**
     * Display admin notices
     */
    public function displayAdminNotices(): void
    {
        // Check PHP version
        if (version_compare(phpversion(), '8.1', '<')) {
            printf(
                '<div class="notice notice-error"><p>%s</p></div>',
                esc_html__('BookiFlex requires PHP 8.1 or higher. Please upgrade your PHP version.', 'bookiflex')
            );
        }
    }

    /**
     * Check if running in development mode
     */
    abstract public static function isDev(): bool;

    /**
     * Return version of plugin
     */
    public function getVersion(): string
    {
        return $this->version;
    }

    /**
     * Return plugin directory path
     */
    public function getPluginDir(): string
    {
        return $this->pluginDir;
    }

    /**
     * Return plugin URL
     */
    public function getPluginUrl(): string
    {
        return $this->pluginUrl;
    }

    /**
     * Get logger instance from container
     */
    public function getLogger(): ?LoggerInterface
    {
        return $this->container->get(LoggerInterface::class);
    }

    /**
     * Register assets manager
     */
    protected function _registerAssets(): void
    {
        $this->assetsManager = $this->createAssetsManager();
    }

    /**
     * Create version-specific assets manager
     * Must be implemented by child classes
     */
    abstract protected function createAssetsManager(): Admin\AbstractAssetsManager;

    public function getAssetsManager(): Admin\AbstractAssetsManager
    {
        return $this->assetsManager;
    }
}
