<?php
/**
 * 2014 - 2023 Watt Is It
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the MIT License X11
 * that is bundled with this package in the file LICENSE.md.
 * It is also available through the world-wide-web at this URL:
 * https://opensource.org/licenses/mit-license.php
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to contact@paygreen.fr so we can send you a copy immediately.
 *
 * @author    PayGreen <contact@paygreen.fr>
 * @copyright 2014 - 2023 Watt Is It
 * @license   https://opensource.org/licenses/mit-license.php MIT License X11
 * @version   1.3.7
 *
 */

namespace PGI\Impact\PGClient\Services\Factories;

use PGI\Impact\PGClient\Components\Feedback as FeedbackComponent;
use PGI\Impact\PGClient\Components\Request as RequestComponent;
use PGI\Impact\PGClient\Components\Response as ResponseComponent;
use PGI\Impact\PGClient\Components\ValidityRangeList as ValidityRangeListComponent;
use PGI\Impact\PGClient\Exceptions\ResponseFailed as ResponseFailedException;
use PGI\Impact\PGClient\Exceptions\ResponseHTTPError as ResponseHTTPErrorException;
use PGI\Impact\PGClient\Exceptions\ResponseMalformed as ResponseMalformedException;
use PGI\Impact\PGLog\Interfaces\LoggerInterface;
use PGI\Impact\PGSystem\Components\Bag as BagComponent;
use Exception;

/**
 * Class ResponseFactory
 * @package PGClient\Services\Factories
 */
class ResponseFactory
{
    private static $DEFAULT_CONFIG = array(
        'class' => 'PGI\Impact\PGClient\Components\Response',
        'validity' => '200',
        'strict' => false
    );

    /** @var LoggerInterface Service to log requests, responses and errors. */
    private $logger;

    /** @var BagComponent List of request definitions. */
    private $requestDefinitions = array();

    /** @var BagComponent */
    private $config;

    private $httpCodes = array();

    /**
     * RequestSender constructor.
     * @param LoggerInterface $logger
     */
    public function __construct(
        LoggerInterface $logger,
        array $requestDefinitions,
        array $config,
        $httpCodes = array()
    ) {
        $this->logger = $logger;
        $this->requestDefinitions = new BagComponent($requestDefinitions);

        $this->config = new BagComponent(self::$DEFAULT_CONFIG);
        $this->config->merge($config);

        $this->httpCodes = $httpCodes;
    }

    /**
     * @param FeedbackComponent $feedback
     * @return ResponseComponent
     * @throws ResponseMalformedException
     * @throws ResponseHTTPErrorException
     * @throws ResponseFailedException
     * @throws Exception
     */
    public function build(FeedbackComponent $feedback)
    {
        $responseClass = $this->getResponseClass($feedback->getRequest());

        try {
            /** @var ResponseComponent $response */
            $response = new $responseClass($feedback);

            if (!$response instanceof ResponseComponent) {
                $class = get_class($response);
                throw new Exception(
                    "Response component must inherit from ResponseComponent. '$class' class defined."
                );
            }

            $this->log('info', 'Building api response.', $response->toArray());
        } catch (ResponseMalformedException $exception) {
            $text = "Malformed response. (HTTP : {$this->httpCodes[$feedback->getCode()]})";
            $this->log('critical', $text, $feedback->getContent(), $feedback->getDetails());

            throw $exception;
        }

        if (!$this->isValidResponse($response)) {
            $text = "Invalid response. (HTTP : {$this->httpCodes[$feedback->getCode()]})";
            $this->log('error', $text, $feedback->getContent(), $feedback->getDetails());

            throw new ResponseHTTPErrorException($text, $feedback->getCode());
        }

        if (!$this->isSuccessResponse($response)) {
            $text = "Unsuccessfull response. (HTTP : {$this->httpCodes[$feedback->getCode()]})";
            $this->log('error', $text, $feedback->getContent(), $feedback->getDetails());

            throw new ResponseFailedException($text);
        }

        return $response;
    }

    protected function isValidResponse(ResponseComponent $response)
    {
        $validityRanges = (string) $this->getConfig('validity', $response->getRequest());

        $validityRanges = new ValidityRangeListComponent($validityRanges);

        return $validityRanges->isValid($response->getHTTPCode());
    }

    /**
     * @param ResponseComponent $response
     * @return bool
     * @throws Exception
     */
    protected function isSuccessResponse(ResponseComponent $response)
    {
        $strict = (bool) $this->getConfig('strict', $response->getRequest());

        if (!$strict) {
            return true;
        } elseif (method_exists($response, 'isSuccess')) {
            return $response->isSuccess();
        }

        throw new Exception("Response does not have 'isSuccess' method.");
    }

    /**
     * @param RequestComponent $request
     * @return string
     * @throws Exception
     */
    protected function getResponseClass(RequestComponent $request)
    {
        return (string) $this->getConfig('class', $request);
    }

    protected function getConfig($key, RequestComponent $request)
    {
        $name = $request->getName();
        $value = $this->requestDefinitions["$name.$key"];

        if ($value === null) {
            $value = $this->config[$key];
        }

        return $value;
    }

    /**
     * @param string $level
     * @param string $message
     * @param mixed $result
     * @param array $details
     */
    protected function log($level, $message, $result, array $details = array())
    {
        if ($this->logger !== null) {
            if (empty($details)) {
                $data = $result;
            } else {
                $data = array(
                    'result' => $result,
                    'details' => $details
                );
            }

            call_user_func(array($this->logger, $level), $message, $data);
        }
    }
}
