<?php

namespace App\Services;

use App\Models\AuditLog;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Request;

/**
 * Writes the tamper-evident trail of sensitive actions required by PRD 51 & 78.
 *
 * Logging must never break the operation being logged, so failures here are
 * reported to the application log rather than thrown.
 */
class AuditLogger
{
    public const LOGIN = 'auth.login';
    public const LOGOUT = 'auth.logout';
    public const LOGIN_FAILED = 'auth.login_failed';
    public const ROLE_CHANGED = 'user.role_changed';
    public const APPLICATION_SUBMITTED = 'application.submitted';
    public const APPLICATION_STATUS_CHANGED = 'application.status_changed';
    public const APPLICATION_DECISION = 'application.decision';
    public const PAYMENT_VERIFIED = 'payment.verified';
    public const PAYMENT_RECEIVED = 'payment.received';
    public const DOCUMENT_VERIFIED = 'document.verified';
    public const DOCUMENT_REJECTED = 'document.rejected';
    public const DOCUMENT_DOWNLOADED = 'document.downloaded';
    public const ADMISSION_GRANTED = 'admission.granted';
    public const APPLICATION_REJECTED = 'application.rejected';
    public const PROGRAMME_CHANGED = 'programme.changed';
    public const SETTINGS_CHANGED = 'settings.changed';

    public function log(
        string $action,
        ?Model $subject = null,
        array $oldValues = [],
        array $newValues = [],
    ): ?AuditLog {
        try {
            return AuditLog::create([
                'user_id' => Auth::id(),
                'action' => $action,
                'auditable_type' => $subject ? $subject::class : null,
                'auditable_id' => $subject?->getKey(),
                'old_values' => $oldValues ?: null,
                'new_values' => $newValues ?: null,
                'ip_address' => Request::ip(),
                'user_agent' => substr((string) Request::userAgent(), 0, 500),
            ]);
        } catch (\Throwable $e) {
            Log::error('Audit log write failed', [
                'action' => $action,
                'exception' => $e->getMessage(),
            ]);

            return null;
        }
    }

    /** Convenience wrapper that diffs a model's dirty attributes. */
    public function logChanges(string $action, Model $subject): ?AuditLog
    {
        $changes = $subject->getChanges();
        $original = array_intersect_key($subject->getOriginal(), $changes);

        return $this->log($action, $subject, $original, $changes);
    }
}
