<?php

namespace Sitecake\Services;

use Sitecake\Event\Event;
use Sitecake\Event\EventManager;
use Sitecake\Exception\MissingActionException;
use Sitecake\Http\JsonResponse;
use Sitecake\Http\RedirectResponse;
use Sitecake\Http\Request;
use Sitecake\Http\Response;
use Sitecake\Site;
use Sitecake\Util\InstanceConfigTrait;
use Sitecake\Util\Text;
use Sitecake\Util\Utils;

abstract class Service implements ServiceInterface
{
    use InstanceConfigTrait;

    /** @var Site */
    protected $site;

    /** @var array Default configuration */
    protected $defaultConfig = [];

    /** @var string Alias under which this service is registered */
    protected $alias;

    /** @var EventManager Per-service event manager */
    protected $eventManager;

    public function __construct(Site $site, $alias, array $config = [])
    {
        $this->site  = $site;
        $this->alias = $alias;
        $this->setConfig($config);
        // Register self as listener on the per-service event manager
        $this->getEventManager()->on($this);
    }

    // ------------------------------------------------------------------ //
    //  Event management (inlined replacement for EventDispatcherTrait)
    // ------------------------------------------------------------------ //

    protected function getEventManager(): EventManager
    {
        if ($this->eventManager === null) {
            $this->eventManager = new EventManager();
        }
        return $this->eventManager;
    }

    protected function dispatchEvent(string $name, array $data = []): Event
    {
        return $this->getEventManager()->dispatch($name, $data, $this);
    }

    /** {@inheritdoc} */
    public function implementedEvents(): array
    {
        return [
            'Service.beforeActionInvoke' => 'beforeAction',
        ];
    }

    /**
     * Called before each action invocation.
     * May return a Response to short-circuit the action.
     */
    public function beforeAction(Event $event)
    {
        return null;
    }

    // ------------------------------------------------------------------ //
    //  Service contract
    // ------------------------------------------------------------------ //

    /** {@inheritdoc} */
    public function isAuthRequired($action): bool
    {
        return true;
    }

    protected function getName(): string
    {
        return ucfirst(substr(Text::camelize($this->alias, '_'), 1));
    }

    protected function actionExists(string $action): bool
    {
        if (!is_subclass_of($this, Service::class)) {
            return false;
        }
        return Utils::hasPublicMethod($this, $action);
    }

    /** {@inheritdoc} */
    public function invokeAction($action, Request $request): Response
    {
        if (!$this->actionExists($action)) {
            throw new MissingActionException([
                'action'  => $action,
                'service' => $this->getName(),
            ], 400);
        }

        $beforeInvokeEvent = $this->dispatchEvent('Service.beforeActionInvoke', compact('request'));
        if ($beforeInvokeEvent->getResult() instanceof Response) {
            return $beforeInvokeEvent->getResult();
        }

        $callable = [$this, $action];
        return $callable($request);
    }

    // ------------------------------------------------------------------ //
    //  Response helpers
    // ------------------------------------------------------------------ //

    protected function json(Request $req, $data, int $status = 200): JsonResponse
    {
        $resp = new JsonResponse($data, $status);
        if ($req->query->has('callback')) {
            $resp->setCallback($req->query->get('callback'));
        }
        return $resp;
    }

    protected function redirect(string $url, int $status = 302, array $headers = []): RedirectResponse
    {
        return new RedirectResponse($url, $status, $headers);
    }

    protected function response(string $content): Response
    {
        return new Response($content);
    }
}
