<?php

namespace onespace\tools\rabbit\services\base;

use Interop\Amqp\Impl\AmqpMessage;
use onespace\tools\rabbit\helpers\CLIHelper;
use onespace\tools\rabbit\services\interfaces\ServiceInterface;
use OS\PB\Packet;
use stdClass;
use Throwable;

abstract class BaseRabbitService implements ServiceInterface {
    use CLIHelper;

    protected readonly AmqpMessage $message;
    protected readonly string $rawBody;
    protected readonly array $headers;
    protected readonly ?string $routingKey;

    /**
     * Where the decoded $rawBody is contained.
     *
     * @readonly
     * @access  protected
     */

    protected readonly array|stdClass|Packet $body;

    /**
     * Holds any error messages recorded by this service
     *
     * @var array   $errorMessages  Default: []
     *
     * @access  protected
     */

    protected array $errorMessages = [];

    /**
     * Assist with construction errors, indicates if everything constructed properly
     */
    private bool $constructed = true;

    /**
     * Lists any reasons given for construction errors.
     */
    private array $constructErrorReasons = [];

    /**
     * Each class must declare a class to decode the body of the message.
     *
     * This cannot be done directly here because either JSON or Protobufs
     * may be sent down.
     *
     * @access  public
     */
    abstract public function decodeRawBody(): array|stdClass|Packet;

    /**
     * {@inheritdoc}
     */
    public function __construct(AmqpMessage $message) {
        try {
            $this->message = $message;
            $this->rawBody = $message->getBody();
            $this->headers = $message->getHeaders();
            $this->routingKey = $message->getRoutingKey();

            $this->body = $this->decodeRawBody();
        } catch (\Throwable $th) {
            $this->setConstructionError($th);
        }
    }

    /**
     * Pass this in the catch of a try catch block, to handle any errors that have occured.
     */
    protected function setConstructionError(Throwable $th): void {
        $this->constructErrorReasons[] = $th->getMessage();
        $this->constructed = false;
    }

    protected function isConstructedProperly(): bool {
        if (!$this->constructed) {
            $this->writeData($this->constructErrorReasons);
            $this->writeData($this->body ?? ['body broken']);
        }
        return $this->constructed;
    }

    /**
     * Get all recorded error messages.
     *
     * @return  array
     *
     * @access  public
     */
    public function getErrorMessages(): array {
        return $this->errorMessages;
    }

    /**
     * Sets an error message.
     *
     * @param   mixed   $message
     * @param   string|int|null $id Default is null
     *
     * @access  public
     */
    public function addErrorMessage(mixed $message, string|int|null $id = null): void {
        if ($id === null) {
            $this->errorMessages[] = $message;
        } else {
            $this->errorMessages[$id][] = $message;
        }
    }

    /**
     * Returns the `headers` property.
     *
     * @return  array
     *
     * @access  public
     */
    public function getHeaders(): array {
        return $this->headers;
    }

    /**
     * Returns the `routingKey` property.
     *
     * @return string|null
     *
     * @access  public
     */
    public function getRoutingKey(): ?string {
        return $this->routingKey;
    }

    /**
     * Returns the `body` property.
     *
     * @return  array|stdClass
     *
     * @access  public
     */
    public function getBody(): array|stdClass {
        return $this->body;
    }
}
