<?php

namespace onespace\tools\activeApi\helpers;

use onespace\tools\rabbit\components\Rabbit;
use onespace\tools\rabbit\helpers\CLIHelper;
use Yii;
use yii\helpers\ArrayHelper;
use yii\helpers\Json;

/**
 * Distribution event types.
 * Work around for traits not supporting constants in PHP 8.1
 */
interface EventTypes {
    public const EVENT_TYPE_CREATE = "create";
    public const EVENT_TYPE_UPDATE = "update";
    public const EVENT_TYPE_DELETE = "delete";
    public const EVENT_TYPE_SYNC = "sync";
}

trait OneSpaceDistributor {
    use CLIHelper;
    use ContextHelper;

    /**
     * Whether or not to use cache for user messages
     * @var bool
     */
    public bool $checkCache = true;

    /**
     * Used for caching user messages to prevent sending unnecessary messages.
     * E.g fingerprints which require a user message before the fingerprint message.
     * Intended for the using class to implement.
     * @return void
     */
    abstract public function getCacheKey(): string;

    /**
     * Rabbit Message Headers
     * Intended for the using class to implement.
     * @example ['hash-on' => $primaryKey, 'site' => $siteId];
     * @return array
     */
    abstract public function getHashParams(): array;

    /**
     * Timestamp for Rabbit Messages
     * @var int
     */
    public int $timestamp;

    /**
     * Whether or not this is running in a Yii Console Application
     * @var bool
     */
    private bool $_isConsoleContext = false;

    /**
     * Make sure this is called from the using class
     * @return void
     */
    protected function initializeDistro(): void {
        if ($this->isCli()) {
            $this->_isConsoleContext = true;
        }
        $this->timestamp = time();
    }

    protected function sendUserMessage(
        string $eventType,
        string $owner,
        string $serial,
        string $name,
        ?string $unit, // OneAccess might not have this value, distro doesn't care
        string $startDate,
        string $endDate,
        string $faceUrl = ''
    ): void {

        if ($this->checkCache) {
            if ($this->userMessageSent($eventType)) {
                $this->log("User message for {$eventType} cached! Not emitting...");
                return;
            }
        }

        $this->log("Emitting {$eventType} message for user...");

        $routingKey = "user.{$eventType}";
        $message = [
            'event' => [
                'type' => $eventType,
                'timestamp' => $this->timestamp,
            ],
            'entity' => [
                'type' => 'user',
                'primaryKey' => [
                    'id' => $owner,
                ],
                'attributes' => [
                    'id' => $owner,
                    'tagSerial' => strval($serial),
                    'name' => $name,
                    'startDate' => $startDate,
                    'endDate' => $endDate,
                    'unit' => $unit,
                    'faceUrl' => $faceUrl,
                ],
            ],
            'author' => $this->getAuthor(),
        ];

        if ($eventType === self::EVENT_TYPE_DELETE) {
            unset($message['entity']['attributes']);
        }

        $this->sendRabbitMessage($message, $routingKey, $this->timestamp);
        $this->cacheUserMessage($eventType);
    }

    /**
     * Send fingerprint message
     * @return void
     */
    protected function sendFingerprintMessage(
        string $eventType,
        string $id,
        string $owner,
        string $manufacturer,
        string $fingerId,
        string $signature,
    ): void {

        $this->log("Emitting {$eventType} message for fingerprint");

        $routingKey = "fingerprint.{$eventType}";
        $message = [
            'event' => [
                'type' => $eventType,
                'timestamp' => $this->timestamp,
            ],
            'entity' => [
                'type' => 'fingerprint',
                'primaryKey' => [
                    'id' => $id,
                ],
                'attributes' => [
                    'id' => $id,
                    'userGuid' => $owner,
                    'fingerId' => $fingerId,
                    'manufacturer' => $manufacturer,
                    'data' => $signature,
                ],
            ],
            'author' => $this->getAuthor(),
        ];

        if ($eventType === self::EVENT_TYPE_DELETE) {
            unset($message['entity']['attributes']);
        }

        $this->sendRabbitMessage($message, $routingKey, $this->timestamp);
    }

    /**
     * Send Rfid message
     * @return void
     */
    protected function sendRfidMessage(
        string $eventType,
        string $serial,
        string $name,
        ?string $unit,
        string $startDate,
        string $endDate,
    ): void {

        $this->log("Emitting {$eventType} message for RFID...");

        $serial = strval($serial);

        $routingKey = "card.{$eventType}";
        $message = [
            'event' => [
                'type' => $eventType,
                'timestamp' => $this->timestamp,
            ],
            'entity' => [
                'type' => 'card',
                'primaryKey' => [
                    'serial' => $serial,
                ],
                'attributes' => [
                    'serial' => $serial,
                    'cardSerial' => str_pad($serial, 10, "0", STR_PAD_LEFT),
                    'name' => $name,
                    'startDate' => $startDate,
                    'endDate' => $endDate,
                    'unit' => $unit,
                ],
            ],
            'author' => $this->getAuthor(),
        ];

        if ($eventType === self::EVENT_TYPE_DELETE) {
            unset($message['entity']['attributes']);
        }

        $this->sendRabbitMessage($message, $routingKey, $this->timestamp);
    }

    /**
     * Cache that we've emitted a user message on this site
     * in the last 5 minutes.
     * @return void
     */
    private function cacheUserMessage($eventType): void {
        // $this->log("Caching user message with {$this->getCacheKey()}.{$eventType}");
        if (!Yii::$app->cache->set("{$this->getCacheKey()}.{$eventType}", true, 10)) {
            $this->log("Error saving to cache!");
        }
    }

    /**
     * Whether or not we've sent a user message in the last 5 minutes.
     * @return bool
     */
    private function userMessageSent($eventType): bool {
        // $cacheResult = Yii::$app->cache->get("{$this->getCacheKey()}.{$eventType}");
        // $this->log("Cache Result: " . json_encode($cacheResult));
        return Yii::$app->cache->get("{$this->getCacheKey()}.{$eventType}") !== false;
    }

    /**
     * Emits a rabbit message using rabbit outbox pattern.
     * @param array $message Body
     * @param string $routingKey
     * @param string $timestamp
     * @return void
     */
    private function sendRabbitMessage(array $message, string $routingKey, string $timestamp): void {

        /** @var Rabbit $rabbit */
        $rabbit = Yii::$app->rabbit;

        // $this->log("Emitting message: " . Json::encode([
        //     'Destination' => 'onespace.biometrics',
        //     'Message' => $message,
        //     'Routing Key' => $routingKey,
        //     'Headers' => $this->getHashParams(),
        //     'Timestamp' => $timestamp,
        // ]));

        $rabbit->outboxSendMessage(
            "onespace.biometrics",
            Json::encode($message),
            $routingKey,
            $this->getHashParams(),
            $timestamp
        );
    }

    private function log($message): void {
        if ($this->_isConsoleContext) {
            $this->writeln($message);
        } else {
            Yii::info($message, __METHOD__);
        }
    }

    private function getAuthor(): array {
        return ArrayHelper::toArray(Yii::$app->user->identity ?? []);
    }
}
