<?php

namespace Kylin\Activity;

use Closure;
use DateTimeInterface;
use Illuminate\Contracts\Config\Repository;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Conditionable;
use Illuminate\Support\Traits\Macroable;
use Kylin\Activity\Contracts\Activity as ActivityContract;

/**
 * 活动日志记录器
 *
 * 核心日志记录服务，负责创建、配置和持久化活动日志条目。
 * 支持链式调用配置日志属性：执行者（causer）、主体（subject）、
 * 日志名称、事件类型、自定义属性等。
 *
 * 使用示例：
 *  activity()->performedOn($order)->causedBy($user)->log('订单已创建');
 *
 * @mixin \Kylin\Activity\PendingActivityLog
 */
class ActivityLogger
{
    use Conditionable;
    use Macroable;

    /** 默认日志名称 */
    protected ?string $defaultLogName = null;

    /** 执行者解析器 */
    protected CauserResolver $causerResolver;

    /** 日志开关状态 */
    protected ActivityLogStatus $logStatus;

    /** 当前活动日志实例 */
    protected ?ActivityContract $activity = null;

    /** 日志批次管理 */
    protected LogBatch $batch;

    /**
     * @param  Repository         $config          配置仓库
     * @param  ActivityLogStatus  $logStatus       日志开关状态
     * @param  LogBatch           $batch           批次管理
     * @param  CauserResolver     $causerResolver  执行者解析器
     */
    public function __construct(Repository $config, ActivityLogStatus $logStatus, LogBatch $batch, CauserResolver $causerResolver)
    {
        $this->causerResolver = $causerResolver;

        $this->batch = $batch;

        $this->defaultLogName = $config['kylin-activity.default_log_name'] ?? 'default';

        $this->logStatus = $logStatus;
    }

    /**
     * 设置日志开关状态
     */
    public function setLogStatus(ActivityLogStatus $logStatus): static
    {
        $this->logStatus = $logStatus;

        return $this;
    }

    /**
     * 设置日志主体（被操作的对象模型）
     */
    public function performedOn(Model $model): static
    {
        $this->getActivity()->subject()->associate($model);

        return $this;
    }

    /**
     * performedOn 的别名
     */
    public function on(Model $model): static
    {
        return $this->performedOn($model);
    }

    /**
     * 设置日志执行者（谁执行的操作）
     *
     * @param  Model|int|string|null  $modelOrId  模型实例、用户ID 或 null
     */
    public function causedBy(Model | int | string | null $modelOrId): static
    {
        if ($modelOrId === null) {
            return $this;
        }

        $model = $this->causerResolver->resolve($modelOrId);

        $this->getActivity()->causer()->associate($model);

        return $this;
    }

    /**
     * causedBy 的别名
     */
    public function by(Model | int | string | null $modelOrId): static
    {
        return $this->causedBy($modelOrId);
    }

    /**
     * 匿名执行者（不关联任何用户）
     */
    public function causedByAnonymous(): static
    {
        $activity = $this->getActivity();

        $activity->causer_id = null;
        $activity->causer_type = null;

        return $this;
    }

    public function byAnonymous(): static
    {
        return $this->causedByAnonymous();
    }

    public function event(string $event): static
    {
        return $this->setEvent($event);
    }

    public function setEvent(string $event): static
    {
        $this->getActivity()->event = $event;

        return $this;
    }

    public function withProperties(mixed $properties): static
    {
        $this->getActivity()->properties = collect($properties);

        return $this;
    }

    public function withProperty(string $key, mixed $value): static
    {
        $this->getActivity()->properties = $this->getActivity()->properties->put($key, $value);

        return $this;
    }

    public function createdAt(DateTimeInterface $dateTime): static
    {
        $this->getActivity()->created_at = Carbon::instance($dateTime);

        return $this;
    }

    public function useLog(?string $logName): static
    {
        $this->getActivity()->log_name = $logName;

        return $this;
    }

    public function inLog(?string $logName): static
    {
        return $this->useLog($logName);
    }

    public function tap(callable $callback, ?string $eventName = null): static
    {
        call_user_func($callback, $this->getActivity(), $eventName);

        return $this;
    }

    public function enableLogging(): static
    {
        $this->logStatus->enable();

        return $this;
    }

    public function disableLogging(): static
    {
        $this->logStatus->disable();

        return $this;
    }

    public function log(string $description): ?ActivityContract
    {
        if ($this->logStatus->disabled()) {
            return null;
        }

        $activity = $this->getActivity();

        $activity->description = $this->replacePlaceholders(
            $activity->description ?? $description,
            $activity
        );

        if (isset($activity->subject) && method_exists($activity->subject, 'tapActivity')) {
            $this->tap([$activity->subject, 'tapActivity'], $activity->event ?? '');
        }

        // 依据《个人信息保护法》第二十八条，保存前对敏感字段进行脱敏处理
        $activity->properties = $this->redactSensitiveData($activity->properties);

        $activity->save();

        $this->activity = null;

        return $activity;
    }

    /**
     * 敏感数据脱敏
     *
     * 依据《个人信息保护法》第二十八条，禁止在日志中记录敏感个人信息明文。
     * 对配置中指定的敏感字段值进行脱敏替换。
     *
     * @param  \Illuminate\Support\Collection|null  $properties  日志属性
     * @return \Illuminate\Support\Collection|null
     */
    protected function redactSensitiveData(?Collection $properties): ?Collection
    {
        if ($properties === null || $properties->isEmpty()) {
            return $properties;
        }

        $sensitiveFields = config('kylin-activity.sensitive_fields', []);

        if (empty($sensitiveFields)) {
            return $properties;
        }

        return $properties->map(function ($value, $key) use ($sensitiveFields) {
            // 递归处理深层嵌套数组（支持任意深度）
            if (is_array($value)) {
                return $this->redactDeepArray($value, $sensitiveFields);
            }

            // 处理顶层字段
            if ($this->isSensitiveField($key, $sensitiveFields)) {
                return '[REDACTED]';
            }

            return $value;
        })->toBase();
    }

    /**
     * 递归脱敏深层嵌套数组
     *
     * 依据《个人信息保护法》第二十八条，敏感个人信息需脱敏处理。
     * 支持任意深度的嵌套数组，确保所有层级的敏感字段均被脱敏。
     */
    protected function redactDeepArray(array $array, array $sensitiveFields): array
    {
        $result = [];

        foreach ($array as $key => $value) {
            if (is_array($value)) {
                // 递归处理更深层嵌套
                $result[$key] = $this->redactDeepArray($value, $sensitiveFields);
            } elseif ($this->isSensitiveField($key, $sensitiveFields)) {
                $result[$key] = '[REDACTED]';
            } else {
                $result[$key] = $value;
            }
        }

        return $result;
    }

    /**
     * 判断字段是否为敏感字段
     *
     * 支持精确匹配和模糊匹配（包含关系）。
     */
    protected function isSensitiveField(string $fieldName, array $sensitiveFields): bool
    {
        $fieldNameLower = mb_strtolower($fieldName);

        foreach ($sensitiveFields as $sensitive) {
            if ($fieldNameLower === mb_strtolower($sensitive)
                || str_contains($fieldNameLower, mb_strtolower($sensitive))
            ) {
                return true;
            }
        }

        return false;
    }

    public function withoutLogs(Closure $callback): mixed
    {
        if ($this->logStatus->disabled()) {
            return $callback();
        }

        $this->logStatus->disable();

        try {
            return $callback();
        } finally {
            $this->logStatus->enable();
        }
    }

    protected function replacePlaceholders(string $description, ActivityContract $activity): string
    {
        return preg_replace_callback('/:[a-z0-9._-]+(?<![.])/i', function ($match) use ($activity) {
            $match = $match[0];

            $attribute = Str::before(Str::after($match, ':'), '.');

            if (! in_array($attribute, ['subject', 'causer', 'properties'])) {
                return $match;
            }

            $propertyName = substr($match, strpos($match, '.') + 1);

            $attributeValue = $activity->$attribute;

            if (is_null($attributeValue)) {
                return $match;
            }

            return data_get($attributeValue, $propertyName, $match);
        }, $description);
    }

    protected function getActivity(): ActivityContract
    {
        if (! $this->activity instanceof ActivityContract) {
            $this->activity = ActivitylogServiceProvider::getActivityModelInstance();
            $this
                ->useLog($this->defaultLogName)
                ->withProperties([])
                ->causedBy($this->causerResolver->resolve());

            $this->activity->batch_uuid = $this->batch->getUuid();
        }

        return $this->activity;
    }
}
