<?php

namespace App\Libraries\Blockchain;

use App\Models\BlockchainLedgerModel;

class SqlLedger
{
    private BlockchainLedgerModel $ledgerModel;

    public function __construct()
    {
        $this->ledgerModel = model(BlockchainLedgerModel::class);
    }

    /**
     * @param array<string,mixed> $payload
     * @return array{index:int,hash:string,previous_hash:string}
     */
    public function appendBlock(string $txType, array $payload, ?string $entityUuid, int $entityVersion, ?int $actorIdx, ?int $lgIdx): array
    {
        $latest = $this->ledgerModel->orderBy('bl_idx', 'DESC')->first();
        // 원장이 비어 있으면 $latest 가 null — $latest->bl_hash 는 PHP 8에서 치명 오류(? 는 ?? 와 달리 속성 접근 자체가 먼저 평가됨)
        $previousHash = ($latest === null || ! isset($latest->bl_hash) || (string) $latest->bl_hash === '')
            ? str_repeat('0', 64)
            : (string) $latest->bl_hash;
        $now = date('Y-m-d H:i:s');

        $normalizedPayload = $this->normalizeArray($payload);
        $payloadJson = json_encode($normalizedPayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '{}';

        $hashInput = implode('|', [
            $now,
            $txType,
            $entityUuid ?? '',
            (string) $entityVersion,
            $payloadJson,
            $previousHash,
            '0',
        ]);
        $currentHash = hash('sha256', $hashInput);

        $this->ledgerModel->insert([
            'bl_created_at'     => $now,
            'bl_tx_type'        => $txType,
            'bl_entity_uuid'    => $entityUuid,
            'bl_entity_version' => $entityVersion,
            'bl_payload'        => $payloadJson,
            'bl_previous_hash'  => $previousHash,
            'bl_hash'           => $currentHash,
            'bl_nonce'          => 0,
            'bl_actor_idx'      => $actorIdx,
            'bl_lg_idx'         => $lgIdx,
        ]);

        return [
            'index' => (int) $this->ledgerModel->getInsertID(),
            'hash' => $currentHash,
            'previous_hash' => $previousHash,
        ];
    }

    /**
     * @param array<string,mixed> $data
     * @return array<string,mixed>
     */
    private function normalizeArray(array $data): array
    {
        ksort($data);
        foreach ($data as $key => $value) {
            if (is_array($value)) {
                if ($this->isAssoc($value)) {
                    /** @var array<string,mixed> $assoc */
                    $assoc = $value;
                    $data[$key] = $this->normalizeArray($assoc);
                } else {
                    $normalizedList = [];
                    foreach ($value as $item) {
                        if (is_array($item) && $this->isAssoc($item)) {
                            /** @var array<string,mixed> $assoc */
                            $assoc = $item;
                            $normalizedList[] = $this->normalizeArray($assoc);
                        } else {
                            $normalizedList[] = $item;
                        }
                    }
                    $data[$key] = $normalizedList;
                }
            }
        }

        return $data;
    }

    /**
     * @param array<mixed> $array
     */
    private function isAssoc(array $array): bool
    {
        if ($array === []) {
            return false;
        }

        return array_keys($array) !== range(0, count($array) - 1);
    }
}
