<?php

namespace App\Models;

use App\Services\Audit\AuditEventBuilder;
use App\Support\Audit\AuditActionId;
use App\Support\Audit\AuditActorSnapshot;
use App\Support\Audit\AuditActorType;
use App\Support\Audit\AuditOutcome;
use App\Support\Audit\AuditTargetSnapshot;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str;

class AuditLog extends Model
{
    use HasFactory;

    /**
     * @var array<int, string>
     */
    private const INTERNAL_METADATA_KEYS = [
        '_actor_type',
        '_dedupe_key',
    ];

    protected $guarded = [];

    protected $casts = [
        'actor_type' => AuditActorType::class,
        'metadata' => 'array',
        'outcome' => AuditOutcome::class,
        'recorded_at' => 'datetime',
    ];

    protected static function booted(): void
    {
        static::creating(function (self $auditLog): void {
            if ($auditLog->workspace_id === null && is_numeric($auditLog->managed_environment_id)) {
                $workspaceId = ManagedEnvironment::query()
                    ->whereKey((int) $auditLog->managed_environment_id)
                    ->value('workspace_id');

                if (is_numeric($workspaceId)) {
                    $auditLog->workspace_id = (int) $workspaceId;
                }
            }

            $derived = app(AuditEventBuilder::class)->fillMissingDerivedAttributes($auditLog->getAttributes());

            $auditLog->forceFill([
                'workspace_id' => $derived['workspace_id'] ?? $auditLog->workspace_id,
                'actor_type' => $derived['actor_type'] ?? $auditLog->actor_type,
                'actor_label' => $derived['actor_label'] ?? $auditLog->actor_label,
                'resource_type' => $derived['resource_type'] ?? $auditLog->resource_type,
                'resource_id' => $derived['resource_id'] ?? $auditLog->resource_id,
                'target_label' => $derived['target_label'] ?? $auditLog->target_label,
                'status' => $derived['status'] ?? $auditLog->status,
                'outcome' => $derived['outcome'] ?? $auditLog->outcome,
                'summary' => $derived['summary'] ?? $auditLog->summary,
                'metadata' => $derived['metadata'] ?? $auditLog->metadata,
                'operation_run_id' => $derived['operation_run_id'] ?? $auditLog->operation_run_id,
                'recorded_at' => $derived['recorded_at'] ?? $auditLog->recorded_at,
            ]);
        });
    }

    public function tenant(): BelongsTo
    {
        return $this->belongsTo(ManagedEnvironment::class, 'managed_environment_id');
    }

    public function workspace(): BelongsTo
    {
        return $this->belongsTo(Workspace::class);
    }

    public function operationRun(): BelongsTo
    {
        return $this->belongsTo(OperationRun::class);
    }

    public function scopeForWorkspace(Builder $query, int $workspaceId): Builder
    {
        return $query->where('workspace_id', $workspaceId);
    }

    public function scopeForTenant(Builder $query, int $tenantId): Builder
    {
        return $query->where('managed_environment_id', $tenantId);
    }

    public function scopeLatestFirst(Builder $query): Builder
    {
        return $query->orderByDesc('recorded_at')->orderByDesc('id');
    }

    public function summaryText(): string
    {
        if (filled($this->summary)) {
            return $this->formattedSummary((string) $this->summary);
        }

        return AuditActionId::summaryFor(
            action: (string) $this->action,
            targetLabel: $this->targetDisplayLabel(),
            targetType: is_string($this->resource_type) ? $this->resource_type : null,
            context: is_array($this->metadata) ? $this->metadata : [],
        );
    }

    public function normalizedOutcome(): AuditOutcome
    {
        return $this->outcome instanceof AuditOutcome
            ? $this->outcome
            : AuditOutcome::normalize($this->outcome ?? $this->status);
    }

    public function actorSnapshot(): AuditActorSnapshot
    {
        $type = $this->actor_type instanceof AuditActorType
            ? $this->actor_type
            : (is_string($this->actor_type) && trim($this->actor_type) !== ''
                ? AuditActorType::normalize($this->actor_type)
                : AuditActorType::infer(
                    action: is_string($this->action) ? $this->action : null,
                    actorId: is_numeric($this->actor_id) ? (int) $this->actor_id : null,
                    actorEmail: is_string($this->actor_email) ? $this->actor_email : null,
                    actorName: is_string($this->actor_name ?? null)
                        ? (string) $this->actor_name
                        : null,
                    context: is_array($this->metadata) ? $this->metadata : [],
                ));

        return AuditActorSnapshot::fromLegacy(
            type: $type,
            id: is_numeric($this->actor_id) ? (int) $this->actor_id : null,
            email: is_string($this->actor_email) ? $this->actor_email : null,
            label: is_string($this->actor_label ?? $this->actor_name ?? null)
                ? (string) ($this->actor_label ?? $this->actor_name)
                : null,
        );
    }

    public function targetSnapshot(): AuditTargetSnapshot
    {
        $type = is_string($this->resource_type) ? $this->resource_type : null;
        $id = is_string($this->resource_id) || is_numeric($this->resource_id)
            ? (string) $this->resource_id
            : null;
        $label = is_string($this->target_label) ? $this->target_label : null;

        if ($type === 'workspace_setting') {
            $label = $this->formattedWorkspaceSettingLabel($label ?? $id);
        }

        return new AuditTargetSnapshot(
            type: $type,
            id: $id,
            label: $label,
        );
    }

    public function actorDisplayLabel(): string
    {
        return $this->actorSnapshot()->labelOrFallback();
    }

    public function targetDisplayLabel(): ?string
    {
        return $this->targetSnapshot()->labelOrFallback();
    }

    /**
     * @return list<array{label: string, value: string|int|float|bool}>
     */
    public function contextItems(): array
    {
        $metadata = is_array($this->metadata) ? $this->metadata : [];
        $items = [];
        $seen = [];

        foreach ([
            'reason',
            'reason_code',
            'before_status',
            'after_status',
            'from_role',
            'to_role',
            'item_count',
            'policy_count',
            'assignment_count',
            'status',
            'source',
            'attempted_action',
        ] as $key) {
            $value = $metadata[$key] ?? null;

            if (! is_scalar($value) || $value === '') {
                continue;
            }

            $items[] = [
                'label' => ucfirst(str_replace('_', ' ', $key)),
                'value' => $value,
            ];
            $seen[] = $key;
        }

        foreach ($metadata as $key => $value) {
            if (
                in_array($key, $seen, true)
                || in_array($key, ['before', 'after'], true)
                || str_starts_with((string) $key, '_')
                || in_array((string) $key, self::INTERNAL_METADATA_KEYS, true)
            ) {
                continue;
            }

            if (! is_scalar($value) || $value === '') {
                continue;
            }

            $items[] = [
                'label' => ucfirst(str_replace('_', ' ', (string) $key)),
                'value' => $value,
            ];

            if (count($items) >= 10) {
                break;
            }
        }

        return $items;
    }

    /**
     * @return array<string, string|int|null>
     */
    public function technicalMetadata(): array
    {
        return array_filter([
            'Event type' => $this->action,
            'Legacy status' => $this->status,
            'Outcome' => $this->normalizedOutcome()->value,
            'Actor id' => is_numeric($this->actor_id) ? (int) $this->actor_id : null,
            'Target type' => $this->resource_type,
            'Target id' => $this->resource_id,
            'Operation run' => is_numeric($this->operation_run_id) ? (int) $this->operation_run_id : null,
            'ManagedEnvironment scope' => is_numeric($this->managed_environment_id) ? (int) $this->managed_environment_id : null,
            'Workspace scope' => is_numeric($this->workspace_id) ? (int) $this->workspace_id : null,
        ], static fn (mixed $value): bool => $value !== null && $value !== '');
    }

    private function formattedSummary(string $summary): string
    {
        if ($this->resource_type !== 'workspace_setting') {
            return $summary;
        }

        $rawTarget = $this->rawWorkspaceSettingTarget();
        $formattedTarget = $this->formattedWorkspaceSettingLabel($rawTarget);

        if ($rawTarget === null || $formattedTarget === null || $rawTarget === $formattedTarget) {
            return $summary;
        }

        return str_replace($rawTarget, $formattedTarget, $summary);
    }

    private function rawWorkspaceSettingTarget(): ?string
    {
        if ($this->resource_type !== 'workspace_setting') {
            return null;
        }

        if (is_string($this->target_label) && trim($this->target_label) !== '') {
            return trim($this->target_label);
        }

        if (is_string($this->resource_id) && trim($this->resource_id) !== '') {
            return trim($this->resource_id);
        }

        return null;
    }

    private function formattedWorkspaceSettingLabel(?string $value): ?string
    {
        if (! is_string($value) || trim($value) === '') {
            return null;
        }

        return Str::of(str_replace('.', ' ', trim($value)))->headline()->toString();
    }
}
