<?php

namespace Acms\Services\Logger;

/**
 * 監査ログレコード（audit_log テーブルから取得した連想配列）の url / referer / context / extra /
 * reqHeader / reqBody に対して読み出し時のマスクを適用する。
 *
 * Why not: ACMS_POST_Logger_Info::buildData() は ACMS_RAM の静的呼び出し・App::make() 等の
 * 環境依存を含み、ハンドラ本体として直接テストしにくい（development-guidelines.md
 * 「テストしない対象」参照）。マスク適用だけをこのクラスへ切り出すことで、Filter とプレーンな
 * 連想配列だけに依存する形にし、protected メソッドへの ReflectionClass アクセスに頼らず
 * 単体テスト可能にしている。
 */
class LogRecordScrubber
{
    /**
     * @param array $log
     * @param Filter $filter
     * @return array{url: string, referer: string, extra: string, context: array, reqHeader: array|null, reqBody: array|null}
     */
    public function scrub(array $log, Filter $filter): array
    {
        $result = [
            'url' => $filter->scrubUrl($this->stringOrNull($log, 'audit_log_url')),
            'referer' => $filter->scrubUrl($this->stringOrNull($log, 'audit_log_referer')),
            'extra' => $this->encodeScrubbedJson($filter, $this->stringOrNull($log, 'audit_log_extra')),
            'context' => $filter->scrubLogArray($this->decodeJsonArray($this->stringOrNull($log, 'audit_log_context'))),
            'reqHeader' => null,
            'reqBody' => null,
        ];

        if (isset($log['audit_log_level']) && intval($log['audit_log_level']) >= 250) {
            $result['reqHeader'] = $filter->getSafeHeaders($this->decodeJsonArray($this->stringOrNull($log, 'audit_log_req_header')));
            $result['reqBody'] = $filter->getSafeArray($this->decodeJsonArray($this->stringOrNull($log, 'audit_log_req_body')));
        }

        return $result;
    }

    /**
     * $log[$key] が文字列であればそれを返し、そうでなければ null を返す。
     *
     * audit_log_url 等のカラムはスキーマ上 NULL 許容のため、DB から取得した値が null の
     * ケースをここで一箇所に集約して吸収する。
     *
     * @param array $log
     * @param string $key
     * @return string|null
     */
    private function stringOrNull(array $log, string $key): ?string
    {
        if (!isset($log[$key]) || !is_string($log[$key])) {
            return null;
        }
        return $log[$key];
    }

    /**
     * JSON文字列を配列としてデコードする。デコードに失敗、またはJSON配列以外だった場合は
     * 空配列を返す
     *
     * @param string|null $json
     * @return array
     */
    private function decodeJsonArray(?string $json): array
    {
        if ($json === null || $json === '') {
            return [];
        }
        $decoded = json_decode($json, true);
        return is_array($decoded) ? $decoded : [];
    }

    /**
     * 保存済みのJSON文字列をデコード・スクラブし、再度JSON文字列として返す
     *
     * @param Filter $filter
     * @param string|null $json
     * @return string
     */
    private function encodeScrubbedJson(Filter $filter, ?string $json): string
    {
        $scrubbed = $filter->scrubLogArray($this->decodeJsonArray($json));
        $encoded = json_encode($scrubbed);
        return $encoded === false ? '' : $encoded;
    }
}
