<?php

namespace Ignite\Core\Entities;

use Illuminate\Support\Facades\Config;
use OwenIt\Auditing\Audit as AuditTrait;
use OwenIt\Auditing\Contracts\Audit as AuditContract;

/**
 * Ignite\Core\Entities\Audit
 *
 * @property int $id
 * @property int|null $user_id
 * @property int|null $context_id
 * @property string|null $context_type
 * @property string $event
 * @property string $auditable_type
 * @property int $auditable_id
 * @property array|null $old_values
 * @property array|null $new_values
 * @property string|null $url
 * @property string|null $ip_address
 * @property string|null $user_agent
 * @property \Illuminate\Support\Carbon|null $created_at
 * @property \Illuminate\Support\Carbon|null $updated_at
 * @property string|null $user_type
 * @property string|null $tags
 * @property string|null $level
 * @property string|null $description
 * @property-read \Illuminate\Database\Eloquent\Model|\Eloquent $auditable
 * @property-read \Illuminate\Database\Eloquent\Model|\Eloquent $user
 * @method static \Illuminate\Database\Eloquent\Builder|Audit newModelQuery()
 * @method static \Illuminate\Database\Eloquent\Builder|Audit newQuery()
 * @method static \Illuminate\Database\Eloquent\Builder|Audit query()
 * @method static \Illuminate\Database\Eloquent\Builder|Audit whereAuditableId($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Audit whereAuditableType($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Audit whereContextId($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Audit whereContextType($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Audit whereCreatedAt($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Audit whereDescription($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Audit whereEvent($value)
 * @method static Builder|Base whereHasPermission(string $permission, ?\Ignite\Core\Entities\User $user = null)
 * @method static \Illuminate\Database\Eloquent\Builder|Audit whereId($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Audit whereIpAddress($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Audit whereLevel($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Audit whereNewValues($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Audit whereOldValues($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Audit whereTags($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Audit whereUpdatedAt($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Audit whereUrl($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Audit whereUserAgent($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Audit whereUserId($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Audit whereUserType($value)
 * @mixin \Eloquent
 */
class Audit extends Base implements AuditContract
{
    use AuditTrait;

    /**
     * {@inheritdoc}
     */
    protected $table = 'core_audit';

    /**
     * {@inheritdoc}
     */
    protected $casts = [
        'old_values' => 'json',
        'new_values' => 'json',
    ];

    /**
     * Get the readable name for the auditable class entity.
     *
     * @return string
     */
    public function getReadableEntityName()
    {
        return ucwords(snake_case(class_basename($this->auditable_type), ' '));
    }

    /**
     * Get the readable identifier for the audited record.
     *
     * @return string
     */
    public function getReadableIdentifier()
    {
        $join = ' ';
        $field = $this->auditable_type::getAuditFriendlyField();
        if (method_exists($this->auditable_type, 'getAuditFriendlyFieldJoinCharacter')) {
            $join = $this->auditable_type::getAuditFriendlyFieldJoinCharacter();
        }

        if (is_array($field)) {
            foreach ($field as $attribute) {
                $readableValue = $this->getReadableValueByAttributeKey($attribute);
                if (method_exists($this->auditable_type, 'getReadableValueByAttributeKey')) {
                    $readableValue = $this->auditable_type::getReadableValueByAttributeKey($attribute, $this);
                }

                $values[] = $readableValue;
            }
            return implode($join, $values);
        }

        return $this->getReadableValueByAttributeKey($field);
    }

    /**
     * Get a value from the correct value array by attribute.
     *
     * @param  string  $attribute
     * @param  object|null  $model
     * @return string
     */
    protected function getReadableValueByAttributeKey(string $attribute, object $model = null): string
    {
        if ($this->event === 'deleted') {
            return $this->old_values[$attribute] ?? $attribute;
        }

        return $this->new_values[$attribute] ?? $attribute;
    }

    /**
     * {@inheritdoc}
     */
    public function resolveData(): array
    {
        // Metadata
        $this->data = [
            'audit_id' => $this->id,
            'audit_event' => $this->event,
            'audit_url' => $this->url,
            'audit_ip_address' => $this->ip_address,
            'audit_user_agent' => $this->user_agent,
            'audit_created_at' => $this->serializeDate($this->created_at),
            'audit_updated_at' => $this->serializeDate($this->updated_at),
            'user_id' => $this->getAttribute(Config::get('audit.user.foreign_key', 'user_id')),
        ];

        if ($this->relationLoaded('user') && $this->user) {
            foreach ($this->user->attributesToArray() as $attribute => $value) {
                $this->data['user_'.$attribute] = $value;
            }
        }

        $this->metadata = array_keys($this->data);

        // Modified Auditable attributes
        foreach ($this->new_values as $key => $value) {
            $this->data['new_'.$key] = $value;
        }

        foreach ($this->old_values as $key => $value) {
            $this->data['old_'.$key] = $value;
        }

        $this->modified = array_diff_key(array_keys($this->data), $this->metadata);

        return $this->data;
    }
}
