<?php

declare(strict_types=1);

namespace Gls\GlsPoland\AdePlus\Authorization;

use Gls\GlsPoland\AdePlus\Authorization\Fault\SessionExpiredFault;
use Gls\GlsPoland\AdePlus\Authorization\Fault\SessionNotFoundFault;
use Gls\GlsPoland\AdePlus\UserCredentialsInterface;
use Gls\GlsPoland\Soap\Client\Caller\CallerInterface;
use Gls\GlsPoland\Soap\Client\Exception\SoapFault;
use Gls\GlsPoland\Soap\Client\Type\RequestInterface;
use Gls\GlsPoland\Soap\Client\Type\ResultInterface;

final class AuthorizingCaller implements CallerInterface
{
    private $caller;
    private $userCredentials;
    private $session;
    private $integratorId;

    public function __construct(CallerInterface $caller, UserCredentialsInterface $userCredentials, string $integratorId)
    {
        $this->caller = $caller;
        $this->userCredentials = $userCredentials;
        $this->integratorId = $integratorId;
    }

    public function call(string $method, RequestInterface $request): ResultInterface
    {
        if (!$request instanceof AuthorizedRequest || null !== $request->getSession()) {
            return $this->caller->call($method, $request);
        }

        $authorized = $this->authorize($request);

        try {
            return $this->caller->call($method, $authorized);
        } catch (SoapFault $exception) {
            if (!in_array($exception->getFaultCode(), [SessionNotFoundFault::CODE, SessionExpiredFault::CODE], true)) {
                throw $exception;
            }

            $this->session = null;
            $authorized = $this->authorize($request);

            return $this->caller->call($method, $authorized);
        }
    }

    public function __destruct()
    {
        if (null === $this->session) {
            return;
        }

        try {
            $this->caller->call('adeLogout', new Logout($this->session));
        } catch (\Exception $e) {
            // ignore silently
        }
    }

    /**
     * @template T of AuthorizedRequest
     *
     * @param T $request
     *
     * @return T
     */
    private function authorize(AuthorizedRequest $request): AuthorizedRequest
    {
        if (null === $this->session) {
            $this->login();
        }

        return $request->withSession($this->session);
    }

    private function login(): void
    {
        $result = $this->caller->call('adeLoginIntegrator', new LoginIntegrator(
            $this->userCredentials->getUsername(),
            $this->userCredentials->getPassword(),
            $this->integratorId
        ));

        assert($result instanceof Session);

        $this->session = $result->getValue();
    }
}
