<?php
declare(strict_types=1);

namespace App\Service\Rest\Operation;

use App\Service\Rest\ApiRequestInterface;
use JetBrains\PhpStorm\Pure;
use RuntimeException;
use Symfony\Component\HttpClient\Exception\InvalidArgumentException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;

/**
 * A single operation, corresponding to a single request
 * to a REST API (Json only)
 */
abstract class BaseRestOperation
{
    public const STATUS_READY = 0;
    public const STATUS_PENDING = 1;
    public const STATUS_DONE = 2;
    public const STATUS_ERROR = 3;

    protected int $status = self::STATUS_READY;
    protected string $errorMessage = "";

    /**
     * @param string $label
     * @param string $method
     * @param string $path
     * @param array<mixed> $initialData
     * @param array<mixed> $parameters
     * @param array<mixed> $options
     */
    public function __construct(
        protected string $label,
        protected string $method,
        protected string $path,
        protected array  $initialData = [],
        protected array  $parameters = [],
        protected array  $options = []
    ) {}

    /**
     * Execute the operation and update its status according to the result
     *
     * @param ApiRequestInterface $apiService
     * @return ResponseInterface
     */
    public function execute(ApiRequestInterface $apiService): ResponseInterface
    {
        $this->status = self::STATUS_PENDING;
        try {
            $response = $apiService->request($this->method, $this->path, $this->parameters, $this->options);

            if ($response->getStatusCode() === 200) {
                $this->status = self::STATUS_DONE;
            } else {
                $this->status = self::STATUS_ERROR;
                $this->errorMessage = 'Error ' . $response->getStatusCode() . ' : ' . $response->getContent();
                throw new HttpException($response->getStatusCode(), $response->getContent());
            }

            return $response;
        } catch (
            HttpException |
            ClientExceptionInterface |
            TransportExceptionInterface |
            RedirectionExceptionInterface |
            ServerExceptionInterface |
            InvalidArgumentException
        $e) {
            $this->status = self::STATUS_ERROR;
            $this->errorMessage = '' . $e;
            throw new RuntimeException($e->getMessage());
        }
    }

    /**
     * @return string
     */
    public function getLabel(): string
    {
        return $this->label;
    }

    /**
     * @return int
     */
    public function getStatus(): int
    {
        return $this->status;
    }

    /**
     * @return string
     */
    public function getErrorMessage(): string
    {
        return $this->errorMessage;
    }

    /**
     * @return string
     */
    public function getMethod(): string
    {
        return $this->method;
    }

    /**
     * @return string
     */
    public function getPath(): string
    {
        return $this->path;
    }

    /**
     * @return array<mixed>
     */
    public function getInitialData(): array
    {
        return $this->initialData;
    }

    /**
     * @return array<mixed>
     */
    public function getParameters(): array
    {
        return $this->parameters;
    }

    /**
     * @return array<mixed>
     */
    public function getOptions(): array
    {
        return $this->options;
    }

    /**
     * Return an array of messages describing the change that this operation will bring
     *
     * @return list<string>
     * @throws \Exception
     */
    abstract public function getChangeLog(): array;

    #[Pure]
    public function __toString(): string {
        return $this->getMethod() . " " . $this->getPath();
    }

    /**
     * @param array<mixed> $initialData
     * @param array<mixed> $newData
     * @param string $prefix
     * @return array<mixed>
     */
    protected static function getRecursiveChangeLog(array $initialData, array $newData, string $prefix = ""): array {
        $messages = [];
        foreach ($newData as $field => $newValue) {
            $fieldLabel = $prefix ? $prefix . '.' . $field : $field;

            if (is_array($newValue)) {
                array_push(
                    $messages,
                    ...self::getRecursiveChangeLog(
                    $initialData[$field] ?? [],
                    $newValue,
                    $fieldLabel)
                );
            } else if (!array_key_exists($field, $initialData)) {
                $messages[] = $fieldLabel . ' : (new) => `' . $newValue . '`';
            } else if ($newValue !== $initialData[$field]) {
                $messages[] = $fieldLabel . ' : `' . $initialData[$field] . '` => `' . $newValue . '`';
            }
        }
        return $messages;
    }
}
