<?php

declare(strict_types=1);

namespace Gls\GlsPoland\Soap\ExtSoapEngine;

use Gls\GlsPoland\Soap\ExtSoapEngine\Configuration\ClassMap\ClassMapCollection;
use Gls\GlsPoland\Soap\ExtSoapEngine\Configuration\TypeConverter;
use Gls\GlsPoland\Soap\ExtSoapEngine\Configuration\TypeConverter\TypeConverterCollection;
use Gls\GlsPoland\Soap\ExtSoapEngine\Exception\UnexpectedConfigurationException;
use Gls\GlsPoland\Soap\ExtSoapEngine\Wsdl\PassThroughWsdlProvider;
use Gls\GlsPoland\Soap\ExtSoapEngine\Wsdl\WsdlProviderInterface;

final class ExtSoapOptions
{
    /**
     * @var string
     */
    private $wsdl;
    /**
     * @var array
     */
    private $options;
    /**
     * @var WsdlProviderInterface
     */
    private $wsdlProvider;

    public function __construct(string $wsdl, array $options = [])
    {
        $this->wsdl = $wsdl;
        $this->options = $options;
        $this->wsdlProvider = new PassThroughWsdlProvider();
    }

    public static function defaults(string $wsdl, array $options = []): self
    {
        $tlsMethod = defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')
            ? STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
            : 33;

        return new self($wsdl, array_merge([
            'trace' => true,
            'exceptions' => true,
            'keep_alive' => true,
            'cache_wsdl' => WSDL_CACHE_DISK, // Avoid memory cache: this causes SegFaults from time to time.
            'features' => SOAP_SINGLE_ELEMENT_ARRAYS,
            'encoding' => 'UTF-8',
            'stream_context' => stream_context_create([
                'ssl' => [
                    'crypto_method' => $tlsMethod,
                    'verify_peer' => true,
                    'verify_peer_name' => true,
                ],
            ]),
            'typemap' => new TypeConverterCollection([
                new TypeConverter\DateTimeTypeConverter(),
                new TypeConverter\DateTypeConverter(),
                new TypeConverter\DecimalTypeConverter(),
                new TypeConverter\DoubleTypeConverter(),
            ]),
        ], $options));
    }

    public function getWsdl(): string
    {
        return $this->wsdlProvider->getWsdl($this->wsdl);
    }

    public function getOptions(): array
    {
        return $this->options;
    }

    public function setWsdlProvider(WsdlProviderInterface $wsdlProvider): self
    {
        $this->wsdlProvider = $wsdlProvider;

        return $this;
    }

    public function getClassMap(): ClassMapCollection
    {
        return $this->getOptionOfTypeWithDefault(
            'classmap',
            ClassMapCollection::class,
            new ClassMapCollection()
        );
    }

    public function setClassMap(ClassMapCollection $classMapCollection): self
    {
        $this->options['classmap'] = $classMapCollection;

        return $this;
    }

    public function getTypeMap(): TypeConverterCollection
    {
        return $this->getOptionOfTypeWithDefault(
            'typemap',
            TypeConverterCollection::class,
            new TypeConverterCollection()
        );
    }

    public function withTypeMap(TypeConverterCollection $typeConverterCollection): self
    {
        $this->options['typemap'] = $typeConverterCollection;

        return $this;
    }

    public function disableWsdlCache(): self
    {
        $this->options['cache_wsdl'] = WSDL_CACHE_NONE;

        return $this;
    }

    /**
     * @template T
     *
     * @param class-string<T> $type
     * @param mixed $default
     *
     * @return T
     */
    private function getOptionOfTypeWithDefault(string $key, string $type, $default)
    {
        $this->options[$key] = $this->options[$key] ?? $default;

        if (!$this->options[$key] instanceof $type) {
            throw UnexpectedConfigurationException::expectedTypeButGot($key, $type, $this->options[$key]);
        }

        return $this->options[$key];
    }
}
