<?php

declare(strict_types=1);

namespace PhpWA\Storage;

use InvalidArgumentException;

final class OutboxEvent
{
    /** @var string */ private $id;
    /** @var string */ private $type;
    /** @var string */ private $payload;
    /** @var int */ private $occurredAt;
    /** @var int */ private $attempts;
    /** @var string */ private $leaseOwner;

    public function __construct(string $id, string $type, string $payload, int $occurredAt, int $attempts, string $leaseOwner)
    {
        if (preg_match('/\A[a-f0-9]{64}\z/D', $id) !== 1 || $occurredAt < 0 || $attempts < 1 || $leaseOwner === '') {
            throw new InvalidArgumentException('Outbox event metadata is invalid.');
        }
        self::assertType($type);
        self::assertPayload($payload);
        $this->id = $id;
        $this->type = $type;
        $this->payload = $payload;
        $this->occurredAt = $occurredAt;
        $this->attempts = $attempts;
        $this->leaseOwner = $leaseOwner;
    }

    public function id(): string { return $this->id; }
    public function type(): string { return $this->type; }
    public function payload(): string { return $this->payload; }
    public function occurredAt(): int { return $this->occurredAt; }
    public function attempts(): int { return $this->attempts; }
    public function leaseOwner(): string { return $this->leaseOwner; }

    /**
     * Portable deterministic ID used by every storage backend. The account
     * hash keeps application identifiers out of the public event key while
     * preserving idempotency during file/PDO migrations.
     */
    public static function incomingId(string $accountHash, string $messageId, string $eventType): string
    {
        if (preg_match('/\A[a-f0-9]{64}\z/D', $accountHash) !== 1 || $messageId === '' || strlen($messageId) > 512
            || preg_match('/[\x00-\x1f\x7f]/', $messageId) === 1) {
            throw new InvalidArgumentException('Incoming outbox identity is invalid.');
        }
        self::assertType($eventType);
        return hash('sha256', "PhpWA\0inbound-event\0" . $accountHash . "\0" . $messageId . "\0" . $eventType);
    }

    public static function assertType(string $type): void
    {
        if (preg_match('/\A[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*\z/D', $type) !== 1 || strlen($type) > 96) {
            throw new InvalidArgumentException('Outbox event type is invalid.');
        }
    }

    public static function assertPayload(string $payload): void
    {
        if ($payload === '' || strlen($payload) > 4194304) {
            throw new InvalidArgumentException('Outbox event payload is empty or too large.');
        }
        try {
            json_decode($payload, true, 128, JSON_THROW_ON_ERROR);
        } catch (\Throwable $error) {
            throw new InvalidArgumentException('Outbox event payload must be valid JSON.', 0, $error);
        }
    }
}
