<?php

namespace GriffinForms\Includes\Pipelines;

use GriffinForms\Log;
use GriffinForms\Traits\Singleton;
use GriffinForms\Includes\Packages\PackageAdmission;

class PostSubmissionPipeline
{
    use Singleton;

    /** @var array<string, PostSubmissionHandlerInterface> */
    protected $handlers = [];
    protected $handler_records = [];

    /** @var \GriffinForms\Log|null */
    protected $log;
    protected $package_admission;

    /**
     * Register a handler with the pipeline.
     */
    public function registerHandler(PostSubmissionHandlerInterface $handler): void
    {
        $handler_record = $this->normalizeHandlerRecord($handler);
        $package_id = $handler_record['package_id'];

        if (!$this->package_admission->canAdmitProvider($handler_record)) {
            $this->logBlockedHandlerRegistration(
                $handler->getId(),
                $package_id,
                (string) $this->package_admission->getProviderBlockingReasonKey($handler_record)
            );
            return;
        }

        $this->handlers[$handler->getId()] = $handler;
        $this->handler_records[$handler->getId()] = $handler_record;
        uasort($this->handlers, static function ($a, $b) {
            return $b->getPriority() <=> $a->getPriority();
        });
    }

    /**
     * Dispatch all registered handlers for the provided context.
     */
    public function dispatch(array $context): void
    {
        foreach ($this->handlers as $handler) {
            $handler_record = $this->handler_records[$handler->getId()] ?? $this->normalizeHandlerRecord($handler);
            if (!$this->package_admission->canAdmitProvider($handler_record)) {
                $this->logBlockedHandlerDispatch(
                    $handler->getId(),
                    $handler_record['package_id'],
                    (string) $this->package_admission->getProviderBlockingReasonKey($handler_record)
                );
                continue;
            }

            if (!$handler->supports($context)) {
                continue;
            }

            try {
                $handler->handle($context);
            } catch (\Throwable $e) {
                $this->logError($context, $handler->getId(), $e->getMessage());
            }
        }
    }

    /**
     * @return PostSubmissionHandlerInterface[]
     */
    public function getHandlers(): array
    {
        return array_values($this->handlers);
    }

    protected function setLog(): void
    {
        $this->log = Log::getInstance();
    }

    protected function setPackageAdmission(): void
    {
        $this->package_admission = PackageAdmission::getInstance();
    }

    private function logError(array $context, string $handler_id, string $message): void
    {
        if (!$this->log) {
            return;
        }

        $submission_id = isset($context['submission_id']) ? (int) $context['submission_id'] : 0;
        $form_id = isset($context['form_id']) ? (int) $context['form_id'] : 0;

        $this->log->add(
            'error',
            'submission',
            $submission_id ?: $form_id,
            get_current_user_id(),
            sprintf('Pipeline handler %s failed: %s', $handler_id, $message),
            'Integration (pipeline)'
        );
    }

    private function resolveHandlerPackageId(PostSubmissionHandlerInterface $handler): string
    {
        if (method_exists($handler, 'getPackageId')) {
            $package_id = sanitize_key((string) $handler->getPackageId());
            if ($package_id !== '') {
                return $package_id;
            }
        }

        return 'core';
    }

    private function handlerRequiresEntitlement(PostSubmissionHandlerInterface $handler): bool
    {
        if (method_exists($handler, 'requiresEntitlement')) {
            return (bool) $handler->requiresEntitlement();
        }

        return false;
    }

    private function logBlockedHandlerRegistration(string $handler_id, string $package_id, string $blocking_reason_key): void
    {
        if (!$this->log) {
            return;
        }

        $this->log->add(
            'warning',
            'workflow',
            0,
            get_current_user_id(),
            sprintf(
                'Workflow [%s] from package [%s] was not admitted during registration because %s.',
                $handler_id,
                $package_id,
                $blocking_reason_key !== '' ? $blocking_reason_key : 'package admission failed'
            ),
            'workflow_dependency'
        );
    }

    private function logBlockedHandlerDispatch(string $handler_id, string $package_id, string $blocking_reason_key): void
    {
        if (!$this->log) {
            return;
        }

        $this->log->add(
            'warning',
            'workflow',
            0,
            get_current_user_id(),
            sprintf(
                'Workflow [%s] from package [%s] was skipped during dispatch because %s.',
                $handler_id,
                $package_id,
                $blocking_reason_key !== '' ? $blocking_reason_key : 'package admission failed'
            ),
            'workflow_dependency'
        );
    }

    private function normalizeHandlerRecord(PostSubmissionHandlerInterface $handler): array
    {
        $provider_record = [
            'id' => $handler->getId(),
            'package_id' => $this->resolveHandlerPackageId($handler),
            'provider_id' => 'workflow.' . sanitize_key($handler->getId()),
            'requires_entitlement' => $this->handlerRequiresEntitlement($handler),
        ];

        if (method_exists($handler, 'getProviderId')) {
            $provider_id = sanitize_key((string) $handler->getProviderId());
            if ($provider_id !== '') {
                $provider_record['provider_id'] = $provider_id;
            }
        }

        if (method_exists($handler, 'getRequirements')) {
            $requirements = $handler->getRequirements();
            if (is_array($requirements)) {
                $provider_record['requires'] = $requirements;
            }
        }

        return $this->package_admission->normalizeProvider($provider_record, 'workflow');
    }
}
