<?php

declare(strict_types=1);

namespace App\Models;

use App\ValueObjects\Money;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;

/**
 * Financial Audit Log Model.
 *
 * Comprehensive audit trail for all income and expense operations.
 * Provides full before/after snapshots and security context for
 * regulatory compliance, fraud detection, and dispute resolution.
 *
 * ACTIONS TRACKED:
 * - created: New record created
 * - updated: Record modified
 * - deleted: Record removed (soft or hard)
 * - locked: Record locked from editing
 * - unlocked: Record unlocked (admin action)
 * - approved: Record approved
 * - rejected: Record rejected
 *
 * @property int $id
 * @property int $company_id
 * @property int $user_id
 * @property string $auditable_type
 * @property int $auditable_id
 * @property string $action
 * @property array|null $previous_data
 * @property array|null $new_data
 * @property array|null $changed_fields
 * @property string|null $amount_before
 * @property string|null $amount_after
 * @property string|null $amount_delta
 * @property string|null $ip_address
 * @property string|null $user_agent
 * @property string|null $session_id
 * @property string|null $reason
 * @property string|null $triggered_by
 * @property Carbon $created_at
 * @property-read Company $company
 * @property-read User $user
 * @property-read Model $auditable
 */
class FinancialAuditLog extends Model
{
    /**
     * Indicates if the model should be timestamped.
     * We only use created_at, no updates allowed.
     */
    public $timestamps = false;

    protected $table = 'financial_audit_logs';

    protected $fillable = [
        'company_id',
        'user_id',
        'auditable_type',
        'auditable_id',
        'action',
        'previous_data',
        'new_data',
        'changed_fields',
        'amount_before',
        'amount_after',
        'amount_delta',
        'ip_address',
        'user_agent',
        'session_id',
        'reason',
        'triggered_by',
        'created_at',
    ];

    protected $casts = [
        'previous_data' => 'array',
        'new_data' => 'array',
        'changed_fields' => 'array',
        'amount_before' => 'decimal:2',
        'amount_after' => 'decimal:2',
        'amount_delta' => 'decimal:2',
        'created_at' => 'datetime',
    ];

    // ═══════════════════════════════════════════════════════════════════════
    // CONSTANTS
    // ═══════════════════════════════════════════════════════════════════════

    public const ACTION_CREATED = 'created';

    public const ACTION_UPDATED = 'updated';

    public const ACTION_DELETED = 'deleted';

    public const ACTION_LOCKED = 'locked';

    public const ACTION_UNLOCKED = 'unlocked';

    public const ACTION_APPROVED = 'approved';

    public const ACTION_REJECTED = 'rejected';

    public const TRIGGER_MANUAL = 'manual';

    public const TRIGGER_SYSTEM = 'system';

    public const TRIGGER_PAYMENT_SYNC = 'payment_sync';

    public const TRIGGER_CREDIT_OPERATION = 'credit_operation';

    public const TRIGGER_AUTO_LOCK = 'auto_lock';

    public const TRIGGER_APPROVAL_FLOW = 'approval_flow';

    // ═══════════════════════════════════════════════════════════════════════
    // RELATIONSHIPS
    // ═══════════════════════════════════════════════════════════════════════

    /**
     * The company this audit log belongs to.
     */
    public function company(): BelongsTo
    {
        return $this->belongsTo(Company::class);
    }

    /**
     * The user who performed the action.
     */
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    /**
     * The audited model (Income or Expense).
     */
    public function auditable(): MorphTo
    {
        return $this->morphTo();
    }

    // ═══════════════════════════════════════════════════════════════════════
    // ACCESSORS
    // ═══════════════════════════════════════════════════════════════════════

    /**
     * Get amount before as Money object.
     */
    public function getAmountBeforeMoney(): ?Money
    {
        return $this->amount_before !== null
            ? Money::of($this->amount_before)
            : null;
    }

    /**
     * Get amount after as Money object.
     */
    public function getAmountAfterMoney(): ?Money
    {
        return $this->amount_after !== null
            ? Money::of($this->amount_after)
            : null;
    }

    /**
     * Get amount delta as Money object.
     */
    public function getAmountDeltaMoney(): ?Money
    {
        return $this->amount_delta !== null
            ? Money::of($this->amount_delta)
            : null;
    }

    /**
     * Get human-readable action label.
     */
    public function getActionLabel(): string
    {
        return match ($this->action) {
            self::ACTION_CREATED => 'Creado',
            self::ACTION_UPDATED => 'Modificado',
            self::ACTION_DELETED => 'Eliminado',
            self::ACTION_LOCKED => 'Bloqueado',
            self::ACTION_UNLOCKED => 'Desbloqueado',
            self::ACTION_APPROVED => 'Aprobado',
            self::ACTION_REJECTED => 'Rechazado',
            default => ucfirst($this->action),
        };
    }

    /**
     * Get action color for UI.
     */
    public function getActionColor(): string
    {
        return match ($this->action) {
            self::ACTION_CREATED => 'success',
            self::ACTION_UPDATED => 'warning',
            self::ACTION_DELETED => 'danger',
            self::ACTION_LOCKED => 'gray',
            self::ACTION_UNLOCKED => 'info',
            self::ACTION_APPROVED => 'success',
            self::ACTION_REJECTED => 'danger',
            default => 'gray',
        };
    }

    /**
     * Get auditable type label.
     */
    public function getAuditableTypeLabel(): string
    {
        return match ($this->auditable_type) {
            Income::class, 'App\\Models\\Income' => 'Ingreso',
            Expense::class, 'App\\Models\\Expense' => 'Egreso',
            default => class_basename($this->auditable_type),
        };
    }

    // ═══════════════════════════════════════════════════════════════════════
    // QUERY SCOPES
    // ═══════════════════════════════════════════════════════════════════════

    /**
     * Scope to filter by company.
     */
    public function scopeForCompany($query, int $companyId)
    {
        return $query->where('company_id', $companyId);
    }

    /**
     * Scope to filter by action type.
     */
    public function scopeForAction($query, string $action)
    {
        return $query->where('action', $action);
    }

    /**
     * Scope to filter by auditable type.
     */
    public function scopeForType($query, string $type)
    {
        return $query->where('auditable_type', $type);
    }

    /**
     * Scope to filter by incomes only.
     */
    public function scopeForIncomes($query)
    {
        return $query->where('auditable_type', Income::class);
    }

    /**
     * Scope to filter by expenses only.
     */
    public function scopeForExpenses($query)
    {
        return $query->where('auditable_type', Expense::class);
    }

    /**
     * Scope to filter by date range.
     */
    public function scopeInDateRange($query, $from, $to)
    {
        return $query->whereBetween('created_at', [$from, $to]);
    }

    /**
     * Scope to filter by user.
     */
    public function scopeByUser($query, int $userId)
    {
        return $query->where('user_id', $userId);
    }

    // ═══════════════════════════════════════════════════════════════════════
    // STATIC FACTORY METHODS
    // ═══════════════════════════════════════════════════════════════════════

    /**
     * Create an audit log entry for a model action.
     */
    public static function log(
        Model $model,
        string $action,
        ?array $previousData = null,
        ?array $newData = null,
        ?string $reason = null,
        string $triggeredBy = self::TRIGGER_MANUAL,
    ): self {
        $user = auth()->user();
        $request = request();

        // Calculate changed fields
        $changedFields = null;
        if ($previousData && $newData) {
            $changedFields = array_keys(array_diff_assoc(
                array_map('strval', $newData),
                array_map('strval', $previousData)
            ));
        }

        // Calculate amount delta
        $amountBefore = $previousData['amount'] ?? null;
        $amountAfter = $newData['amount'] ?? null;
        $amountDelta = null;

        if ($amountBefore !== null && $amountAfter !== null) {
            $amountDelta = (float) $amountAfter - (float) $amountBefore;
        } elseif ($amountAfter !== null) {
            $amountDelta = (float) $amountAfter;
        } elseif ($amountBefore !== null) {
            $amountDelta = -(float) $amountBefore;
        }

        return static::create([
            'company_id' => $model->company_id,
            'user_id' => $user?->id,
            'auditable_type' => get_class($model),
            'auditable_id' => $model->id,
            'action' => $action,
            'previous_data' => $previousData,
            'new_data' => $newData,
            'changed_fields' => $changedFields,
            'amount_before' => $amountBefore,
            'amount_after' => $amountAfter,
            'amount_delta' => $amountDelta,
            'ip_address' => $request?->ip(),
            'user_agent' => $request?->userAgent(),
            'session_id' => session()->getId(),
            'reason' => $reason,
            'triggered_by' => $triggeredBy,
            'created_at' => now(),
        ]);
    }

    /**
     * Log a creation event.
     */
    public static function logCreated(
        Model $model,
        ?string $reason = null,
        string $triggeredBy = self::TRIGGER_MANUAL,
    ): self {
        return static::log(
            model: $model,
            action: self::ACTION_CREATED,
            previousData: null,
            newData: $model->toArray(),
            reason: $reason,
            triggeredBy: $triggeredBy,
        );
    }

    /**
     * Log an update event.
     */
    public static function logUpdated(
        Model $model,
        array $previousData,
        ?string $reason = null,
        string $triggeredBy = self::TRIGGER_MANUAL,
    ): self {
        return static::log(
            model: $model,
            action: self::ACTION_UPDATED,
            previousData: $previousData,
            newData: $model->toArray(),
            reason: $reason,
            triggeredBy: $triggeredBy,
        );
    }

    /**
     * Log a deletion event.
     */
    public static function logDeleted(
        Model $model,
        ?string $reason = null,
        string $triggeredBy = self::TRIGGER_MANUAL,
    ): self {
        return static::log(
            model: $model,
            action: self::ACTION_DELETED,
            previousData: $model->toArray(),
            newData: null,
            reason: $reason,
            triggeredBy: $triggeredBy,
        );
    }

    /**
     * Log a lock event.
     */
    public static function logLocked(
        Model $model,
        string $lockReason,
        string $triggeredBy = self::TRIGGER_AUTO_LOCK,
    ): self {
        return static::log(
            model: $model,
            action: self::ACTION_LOCKED,
            previousData: ['is_locked' => false],
            newData: ['is_locked' => true, 'lock_reason' => $lockReason],
            reason: $lockReason,
            triggeredBy: $triggeredBy,
        );
    }

    /**
     * Log an approval event.
     */
    public static function logApproved(
        Model $model,
        ?string $approvalNotes = null,
        string $triggeredBy = self::TRIGGER_APPROVAL_FLOW,
    ): self {
        return static::log(
            model: $model,
            action: self::ACTION_APPROVED,
            previousData: ['approval_status' => 'pending'],
            newData: ['approval_status' => 'approved'],
            reason: $approvalNotes,
            triggeredBy: $triggeredBy,
        );
    }

    /**
     * Log a rejection event.
     */
    public static function logRejected(
        Model $model,
        string $rejectionReason,
        string $triggeredBy = self::TRIGGER_APPROVAL_FLOW,
    ): self {
        return static::log(
            model: $model,
            action: self::ACTION_REJECTED,
            previousData: ['approval_status' => 'pending'],
            newData: ['approval_status' => 'rejected'],
            reason: $rejectionReason,
            triggeredBy: $triggeredBy,
        );
    }
}
