<?php
/**
 * SimplyIN Payment Module
 *
 * Central processor for coordinating pickup point data across shipping module integrators.
 */
declare(strict_types=1);

namespace Simplyin\Payment\Model;

use Magento\Quote\Api\Data\CartInterface;
use Psr\Log\LoggerInterface;
use Simplyin\Payment\Api\IntegratorInterface;
use Simplyin\Payment\Api\PickupPointProcessorInterface;

/**
 * Class PickupPointProcessor
 *
 * Central processor that coordinates pickup point data processing across different
 * shipping module integrators. Routes pickup point data to the appropriate integrator
 * based on the provider name and handles unknown providers gracefully.
 *
 * Integrators are injected via DI (di.xml) array argument to decouple from concrete
 * classes. If a third-party module is not installed, the integrator's isAvailable()
 * returns false and the integration is skipped without throwing errors.
 *
 * @see Requirements 1.1, 1.2, 1.4, 8.2, 8.3, 8.4
 */
class PickupPointProcessor implements PickupPointProcessorInterface
{
    /**
     * @var array<string, IntegratorInterface>
     */
    private array $integrators = [];

    /**
     * @var array<string, IntegratorInterface>
     */
    private array $providerToIntegratorMap = [];

    /**
     * @var LoggerInterface
     */
    private LoggerInterface $logger;

    /**
     * Constructor
     *
     * @param LoggerInterface $logger
     * @param IntegratorInterface[] $integrators Injected via di.xml argument
     */
    public function __construct(
        LoggerInterface $logger,
        array $integrators = []
    ) {
        $this->logger = $logger;

        // Register all integrators injected via DI
        foreach ($integrators as $integrator) {
            if ($integrator instanceof IntegratorInterface) {
                $this->registerIntegrator($integrator);
            } else {
                $this->logger->warning(
                    sprintf(
                        'PickupPointProcessor: Skipping invalid integrator of type "%s" — does not implement IntegratorInterface',
                        get_debug_type($integrator)
                    )
                );
            }
        }
    }

    /**
     * Register an integrator and build the provider-to-integrator mapping
     *
     * @param IntegratorInterface $integrator
     * @return void
     */
    private function registerIntegrator(IntegratorInterface $integrator): void
    {
        $className = get_class($integrator);
        $this->integrators[$className] = $integrator;

        // Map each provider name to this integrator
        foreach ($integrator->getProviderNames() as $providerName) {
            $normalizedName = strtolower($providerName);
            $this->providerToIntegratorMap[$normalizedName] = $integrator;
        }
    }

    /**
     * Process pickup point data and set extension attributes
     *
     * Routes the pickup point data to the appropriate integrator based on
     * the provider name and sets the required extension attributes on the quote.
     * Handles unknown providers gracefully by logging a warning and returning false.
     *
     * @param CartInterface $quote The quote to set pickup point data on
     * @param array $pickupPointData Pickup point data containing at minimum:
     *                               - lockerId: string - The provider-specific locker/pickup point ID
     *                               - providerName: string - The provider name (will be normalized)
     *                               Additional fields may include address, city, postalCode, etc.
     * @return bool True if the pickup point was successfully processed, false otherwise
     */
    public function process(CartInterface $quote, array $pickupPointData): bool
    {
        // Extract provider name from data (Requirement 1.1)
        $providerName = $this->extractProviderName($pickupPointData);

        if (empty($providerName)) {
            $this->logger->warning(
                'PickupPointProcessor: Unable to extract provider name from pickup point data'
            );
            return false;
        }

        // Normalize provider name to lowercase (Requirement 1.2)
        $normalizedProviderName = $this->normalizeProviderName($providerName);

        $this->logger->info(
            sprintf(
                'PickupPointProcessor: Processing pickup point for provider "%s" (normalized: "%s")',
                $providerName,
                $normalizedProviderName
            )
        );

        // Find the appropriate integrator for this provider
        $integrator = $this->getIntegratorForProvider($normalizedProviderName);

        if ($integrator === null) {
            // Unknown provider - log warning and skip (Requirement 1.4)
            $this->logger->warning(
                sprintf(
                    'PickupPointProcessor: Unknown provider "%s", skipping module-specific integration. Supported providers: %s',
                    $normalizedProviderName,
                    implode(', ', $this->getSupportedProviders())
                )
            );
            return false;
        }

        // Check if the module is available (Requirement 8.2, 8.3)
        if (!$integrator->isAvailable()) {
            // Module not installed - skip without error (Requirement 8.4)
            $this->logger->info(
                sprintf(
                    'PickupPointProcessor: Shipping module for provider "%s" is not installed or enabled, skipping integration',
                    $normalizedProviderName
                )
            );
            return false;
        }

        // Ensure the normalized provider name is in the data for the integrator
        $pickupPointData['providerName'] = $normalizedProviderName;

        // Wrap the integrator call in try-catch to handle potential class-not-found
        // errors if the third-party module's extension attribute interfaces are missing
        try {
            return $integrator->setPickupPoint($quote, $pickupPointData);
        } catch (\Error $e) {
            // Catches TypeError, Error (class not found) from missing extension attributes
            $this->logger->error(
                sprintf(
                    'PickupPointProcessor: Integration error for provider "%s": %s. '
                    . 'Ensure the required shipping module is properly installed.',
                    $normalizedProviderName,
                    $e->getMessage()
                )
            );
            return false;
        }
    }

    /**
     * Get supported provider names
     *
     * Returns an array of all provider names supported by the registered integrators.
     * Provider names are normalized to lowercase.
     *
     * @return array List of supported provider names (e.g., ['inpost', 'orlen', 'orlenpaczka', 'dpd'])
     */
    public function getSupportedProviders(): array
    {
        return array_keys($this->providerToIntegratorMap);
    }

    /**
     * Extract provider name from pickup point data
     *
     * @param array $pickupPointData The pickup point data
     * @return string|null The extracted provider name or null if not found
     */
    private function extractProviderName(array $pickupPointData): ?string
    {
        if (isset($pickupPointData['providerName']) && !empty($pickupPointData['providerName'])) {
            return (string) $pickupPointData['providerName'];
        }

        if (isset($pickupPointData['provider']) && !empty($pickupPointData['provider'])) {
            return (string) $pickupPointData['provider'];
        }

        return null;
    }

    /**
     * Normalize provider name to lowercase
     *
     * @param string $providerName The provider name to normalize
     * @return string The normalized provider name
     */
    private function normalizeProviderName(string $providerName): string
    {
        return strtolower(trim($providerName));
    }

    /**
     * Get the integrator for a given provider name
     *
     * @param string $normalizedProviderName The normalized (lowercase) provider name
     * @return IntegratorInterface|null The integrator or null if not found
     */
    private function getIntegratorForProvider(string $normalizedProviderName): ?IntegratorInterface
    {
        return $this->providerToIntegratorMap[$normalizedProviderName] ?? null;
    }
}
