<?php

namespace App\Traits;

use App\Models\Audit\AuditLog;
use Illuminate\Support\Str;

trait Auditable
{
    protected static function bootAuditable(): void
    {
        static::creating(function ($model) {
            $actorId = static::resolveActorId();
            if ($actorId) {
                if ($model->isFillable('created_by') && ! $model->created_by) {
                    $model->created_by = $actorId;
                }
                if ($model->isFillable('updated_by') && ! $model->updated_by) {
                    $model->updated_by = $actorId;
                }
            }
        });

        static::updating(function ($model) {
            $actorId = static::resolveActorId();
            if ($actorId && $model->isFillable('updated_by')) {
                $model->updated_by = $actorId;
            }
        });

        static::deleting(function ($model) {
            $actorId = static::resolveActorId();
            if ($actorId && $model->isFillable('deleted_by')) {
                $model->deleted_by = $actorId;
                $model->saveQuietly();
            }
        });

        static::created(function ($model) {
            $model->logAuditEvent('created', null, $model->toAuditSnapshot());
        });

        static::updated(function ($model) {
            $dirty = $model->getChanges();
            $excluded = $model->getAuditExcludedFields();
            $changed = array_diff_key($dirty, array_flip($excluded));

            if (empty($changed)) {
                return;
            }

            $before = [];
            $after = [];
            foreach ($changed as $key => $value) {
                $before[$key] = $model->getOriginal($key);
                $after[$key] = $value;
            }

            $model->logAuditEvent('updated', $before, $after);
        });

        static::deleted(function ($model) {
            $model->logAuditEvent('deleted', $model->toAuditSnapshot(), null);
        });
    }

    public function getAuditExcludedFields(): array
    {
        $defaults = [
            'created_at', 'updated_at', 'deleted_at',
            'created_by', 'updated_by', 'deleted_by',
        ];

        $custom = property_exists($this, 'auditExclude') ? $this->auditExclude : [];

        return array_merge($defaults, $custom);
    }

    public function getAuditTargetType(): string
    {
        $className = class_basename(static::class);

        return Str::snake($className);
    }

    protected function toAuditSnapshot(): array
    {
        $excluded = $this->getAuditExcludedFields();

        return array_diff_key($this->attributesToArray(), array_flip($excluded));
    }

    protected function logAuditEvent(string $action, ?array $before, ?array $after): void
    {
        try {
            $tenantId = $this->tenant_id ?? null;
            if (! $tenantId) {
                return;
            }

            $request = request();

            AuditLog::create([
                'tenant_id' => $tenantId,
                'target_type' => $this->getAuditTargetType(),
                'target_id' => $this->getKey(),
                'action' => $action,
                'before' => $before,
                'after' => $after,
                'actor_id' => static::resolveActorId(),
                'ip' => $request?->ip(),
                'ua' => $request?->userAgent(),
                'created_at' => now(),
            ]);
        } catch (\Throwable $e) {
            // 감사 로그 실패는 업무 흐름을 방해하지 않음
        }
    }

    protected static function resolveActorId(): ?int
    {
        return auth()->id();
    }
}
