<?php
declare(strict_types=1);

namespace App\Modules\Audit\Services;

use App\Core\App;
use App\Core\Logger;

final class AuditService
{
    /**
     * Tables we NEVER audit (they'd cause infinite loops or are just noise).
     */
    private const IGNORE_TABLES = [
        'audit_trail',
        'login_attempts',
        'active_sessions',
        'migrations',
        'seeds',
        'async_event_queue',
        'cron_job_log',
        'password_history',
        'sms_log',
        'notification_queue',
    ];

    /**
     * Sensitive fields — values are masked in audit logs.
     */
    private const SENSITIVE_FIELDS = [
        'password_hash',
        'password',
    ];

    /**
     * Fields to skip in change tracking (they change on every save and add noise).
     */
    private const NOISE_FIELDS = [
        'updated_at',
    ];

    /**
     * Log an audit entry directly.
     */
    public static function log(
        string $action,
        ?string $entityType = null,
        ?int $entityId = null,
        ?string $entityLabel = null,
        ?array $before = null,
        ?array $after = null,
        ?string $notes = null
    ): void {
        try {
            $app = App::getInstance();
            $db = $app->db();
            $employee = $app->currentEmployee();
            $session = $app->session();

            // Calculate changed fields
            $changedFields = null;
            if ($before !== null && $after !== null) {
                $changed = self::computeChangedFields($before, $after);
                if (!empty($changed)) {
                    $changedFields = $changed;
                }
            }

            // Mask sensitive fields
            if ($before !== null) $before = self::maskSensitive($before);
            if ($after !== null) $after = self::maskSensitive($after);

            $data = [
                'employee_id'         => $employee ? ($employee->id ?? null) : null,
                'employee_name'       => $employee ? ($employee->full_name_ar ?? null) : null,
                'action'              => $action,
                'entity_type'         => $entityType,
                'entity_id'           => $entityId,
                'entity_label'        => $entityLabel ? mb_substr($entityLabel, 0, 255) : null,
                'before_data_json'    => $before !== null ? json_encode($before, JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR) : null,
                'after_data_json'     => $after !== null ? json_encode($after, JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR) : null,
                'changed_fields_json' => $changedFields !== null ? json_encode($changedFields, JSON_UNESCAPED_UNICODE) : null,
                'ip_address'          => $_SERVER['REMOTE_ADDR'] ?? null,
                'user_agent'          => isset($_SERVER['HTTP_USER_AGENT']) ? mb_substr($_SERVER['HTTP_USER_AGENT'], 0, 500) : null,
                'session_id'          => $session ? ($session->id() ?: null) : null,
                'route'               => $_SERVER['REQUEST_URI'] ?? null,
                'notes'               => $notes,
                'created_at'          => date('Y-m-d H:i:s'),
            ];

            // Direct insert — bypasses hooks to avoid infinite loop
            $columns = array_keys($data);
            $placeholders = array_fill(0, count($columns), '?');
            $sql = sprintf(
                'INSERT INTO `audit_trail` (%s) VALUES (%s)',
                implode(', ', array_map(fn($c) => "`{$c}`", $columns)),
                implode(', ', $placeholders)
            );
            $db->query($sql, array_values($data));

        } catch (\Throwable $e) {
            // Audit must NEVER crash the application
            try {
                Logger::warning('Audit log failed: ' . $e->getMessage());
            } catch (\Throwable $e2) {}
        }
    }

    /**
     * Compute which fields actually changed between two records.
     * Returns array of field names with old/new values.
     */
    private static function computeChangedFields(array $before, array $after): array
    {
        $changed = [];
        $allKeys = array_unique(array_merge(array_keys($before), array_keys($after)));

        foreach ($allKeys as $key) {
            // Skip noise fields
            if (in_array($key, self::NOISE_FIELDS, true)) {
                continue;
            }

            $oldVal = $before[$key] ?? null;
            $newVal = $after[$key] ?? null;

            // Normalize for comparison
            $oldStr = ($oldVal === null) ? '' : (string) $oldVal;
            $newStr = ($newVal === null) ? '' : (string) $newVal;

            if ($oldStr !== $newStr) {
                // Mask sensitive
                if (in_array($key, self::SENSITIVE_FIELDS, true)) {
                    $changed[$key] = ['from' => '***', 'to' => '***'];
                } else {
                    $changed[$key] = [
                        'from' => $oldVal,
                        'to'   => $newVal,
                    ];
                }
            }
        }

        return $changed;
    }

    /**
     * Mask sensitive field values.
     */
    private static function maskSensitive(array $data): array
    {
        foreach (self::SENSITIVE_FIELDS as $field) {
            if (array_key_exists($field, $data)) {
                $data[$field] = '***MASKED***';
            }
        }
        return $data;
    }

    /**
     * Get a human-readable label for a record.
     */
    private static function extractLabel(string $table, array $data): ?string
    {
        // Try common label fields in priority order
        $labelFields = [
            'full_name_ar', 'name_ar', 'username', 'membership_number',
            'form_number', 'branch_code', 'role_code', 'carnet_number',
            'receipt_number', 'rule_code', 'template_code', 'service_code',
            'form_code', 'workflow_code', 'report_code', 'discount_code',
        ];

        foreach ($labelFields as $field) {
            if (!empty($data[$field])) {
                return mb_substr((string) $data[$field], 0, 255);
            }
        }

        return null;
    }

    /**
     * Log login events.
     */
    public static function logLogin(string $username, bool $success, ?string $reason = null): void
    {
        self::log(
            $success ? 'login' : 'login_failed',
            'employee',
            null,
            $username,
            null,
            null,
            $success ? "تسجيل دخول ناجح: {$username}" : "فشل تسجيل الدخول: {$username} - {$reason}"
        );
    }

    /**
     * Log logout events.
     */
    public static function logLogout(): void
    {
        $employee = App::getInstance()->currentEmployee();
        $name = $employee ? ($employee->full_name_ar ?? 'unknown') : 'unknown';
        self::log('logout', 'employee', $employee ? (int) $employee->id : null, $name);
    }

    /**
     * Register database hooks for AUTOMATIC audit logging of ALL operations.
     * This is the nuclear option — every INSERT, UPDATE, DELETE gets logged.
     */
    public static function registerDatabaseHooks(): void
    {
        try {
            $db = App::getInstance()->db();

            if (!$db->tableExists('audit_trail')) {
                return;
            }

            // ══════════════════════════════════════
            // HOOK: After INSERT — log creation
            // ══════════════════════════════════════
            $db->onAfterInsert(function (string $table, array $data, ?int $id) {
                if (in_array($table, self::IGNORE_TABLES, true)) {
                    return;
                }

                $label = self::extractLabel($table, $data);

                self::log(
                    'create',
                    $table,
                    $id,
                    $label,
                    null,   // no before (it's a creation)
                    $data,  // after = the inserted data
                    null
                );
            });

            // ══════════════════════════════════════
            // HOOK: After UPDATE — log changes with before/after
            // ══════════════════════════════════════
            $db->onAfterUpdate(function (string $table, array $changedData, ?int $entityId, ?array $oldRecord, ?array $newRecord) {
                if (in_array($table, self::IGNORE_TABLES, true)) {
                    return;
                }

                // Don't log if nothing actually changed
                if ($oldRecord && $newRecord) {
                    $changed = self::computeChangedFields($oldRecord, $newRecord);
                    if (empty($changed)) {
                        return; // No real changes
                    }
                }

                $label = null;
                if ($newRecord) {
                    $label = self::extractLabel($table, $newRecord);
                } elseif ($oldRecord) {
                    $label = self::extractLabel($table, $oldRecord);
                }

                self::log(
                    'update',
                    $table,
                    $entityId,
                    $label,
                    $oldRecord,  // full record before
                    $newRecord,  // full record after
                    null
                );
            });

            // ══════════════════════════════════════
            // HOOK: After DELETE — log deletion with old data
            // ══════════════════════════════════════
            $db->onAfterDelete(function (string $table, array $oldRecord, ?int $entityId) {
                if (in_array($table, self::IGNORE_TABLES, true)) {
                    return;
                }

                $label = !empty($oldRecord) ? self::extractLabel($table, $oldRecord) : null;

                self::log(
                    'delete',
                    $table,
                    $entityId,
                    $label,
                    $oldRecord,  // what was deleted
                    null,        // nothing after (it's gone)
                    "Deleted from {$table}" . ($entityId ? " ID: {$entityId}" : '')
                );
            });

        } catch (\Throwable $e) {
            try {
                Logger::warning('Failed to register audit hooks: ' . $e->getMessage());
            } catch (\Throwable $e2) {}
        }
    }

    public static function getActionLabel(string $action): string
    {
        return match ($action) {
            'create'       => 'إنشاء',
            'update'       => 'تعديل',
            'delete'       => 'حذف',
            'archive'      => 'أرشفة',
            'login'        => 'تسجيل دخول',
            'logout'       => 'تسجيل خروج',
            'login_failed' => 'فشل تسجيل دخول',
            default        => $action,
        };
    }

    public static function getActionIcon(string $action): string
    {
        return match ($action) {
            'create'       => '➕',
            'update'       => '✏️',
            'delete'       => '🗑️',
            'archive'      => '📦',
            'login'        => '🔑',
            'logout'       => '🚪',
            'login_failed' => '🚫',
            default        => '📋',
        };
    }

    public static function getActionColor(string $action): string
    {
        return match ($action) {
            'create'       => '#059669',
            'update'       => '#0284C7',
            'delete'       => '#DC2626',
            'archive'      => '#D97706',
            'login'        => '#6366F1',
            'logout'       => '#6B7280',
            'login_failed' => '#DC2626',
            default        => '#6B7280',
        };
    }

    public static function getEntityTypeLabel(string $type): string
    {
        return match ($type) {
            'members'              => 'عضو',
            'employees'            => 'موظف',
            'spouses'              => 'زوج/ة',
            'children'             => 'ابن/ابنة',
            'temporary_members'    => 'عضو مؤقت',
            'seasonal_memberships' => 'عضوية موسمية',
            'payments'             => 'دفعة',
            'payment_requests'     => 'طلب دفع',
            'receipts'             => 'إيصال',
            'installment_plans'    => 'خطة تقسيط',
            'installment_schedule' => 'قسط',
            'subscriptions'        => 'اشتراك',
            'fines'                => 'غرامة',
            'divorce_cases'        => 'حالة طلاق',
            'death_cases'          => 'حالة وفاة',
            'waiver_requests'      => 'طلب تنازل',
            'transfer_requests'    => 'طلب نقل/فصل',
            'branches'             => 'فرع',
            'roles'                => 'دور وظيفي',
            'qualifications'       => 'مؤهل',
            'business_rules'       => 'قاعدة عمل',
            'service_catalog'      => 'خدمة',
            'form_submissions'     => 'استمارة',
            'workflow_instances'   => 'مسار عمل',
            'journal_entries'      => 'قيد محاسبي',
            'sports_members'       => 'عضو رياضي',
            'carnets'              => 'كارنيه',
            'employee'             => 'موظف',
            default                => $type,
        };
    }

    public static function getFieldLabel(string $field): string
    {
        return match ($field) {
            'full_name_ar'             => 'الاسم بالعربية',
            'full_name_en'             => 'الاسم بالإنجليزية',
            'national_id'              => 'الرقم القومي',
            'status'                   => 'الحالة',
            'membership_number'        => 'رقم العضوية',
            'form_number'              => 'رقم الاستمارة',
            'branch_id'                => 'الفرع',
            'qualification_id'         => 'المؤهل',
            'membership_value'         => 'قيمة العضوية',
            'membership_type'          => 'نوع العضوية',
            'member_category'          => 'فئة العضو',
            'date_of_birth'            => 'تاريخ الميلاد',
            'gender'                   => 'النوع',
            'phone'                    => 'الهاتف',
            'email'                    => 'البريد الإلكتروني',
            'marital_status'           => 'الحالة الاجتماعية',
            'nationality'              => 'الجنسية',
            'religion'                 => 'الديانة',
            'occupation'               => 'المهنة',
            'residence_address'        => 'عنوان السكن',
            'is_archived'              => 'مؤرشف',
            'archived_at'              => 'تاريخ الأرشفة',
            'archived_by'              => 'أرشف بواسطة',
            'created_at'               => 'تاريخ الإنشاء',
            'updated_at'               => 'تاريخ التعديل',
            'created_by'               => 'أنشئ بواسطة',
            'updated_by'               => 'عُدّل بواسطة',
            'amount'                   => 'المبلغ',
            'payment_type'             => 'نوع الدفعة',
            'payment_method'           => 'طريقة الدفع',
            'payment_date'             => 'تاريخ الدفع',
            'receipt_number'           => 'رقم الإيصال',
            'request_number'           => 'رقم الطلب',
            'addition_fee'             => 'رسوم الإضافة',
            'spouse_order'             => 'ترتيب الزوجة',
            'child_order'              => 'ترتيب الابن',
            'classification'           => 'التصنيف',
            'join_date'                => 'تاريخ الالتحاق',
            'marriage_date'            => 'تاريخ الزواج',
            'age_years'                => 'السن (سنوات)',
            'age_months'               => 'السن (شهور)',
            'category'                 => 'الفئة',
            'notes'                    => 'ملاحظات',
            'is_voided'                => 'ملغى',
            'voided_at'                => 'تاريخ الإلغاء',
            'void_reason'              => 'سبب الإلغاء',
            'description_ar'           => 'الوصف',
            'current_value_json'       => 'القيمة الحالية',
            'rule_code'                => 'كود القاعدة',
            'fee_amount'               => 'مبلغ الرسوم',
            'start_date'               => 'تاريخ البداية',
            'end_date'                 => 'تاريخ النهاية',
            'discount_amount'          => 'مبلغ الخصم',
            'special_discount_id'      => 'خصم خاص',
            'has_championship'         => 'بطولات رياضية',
            'disability_documentation' => 'وثائق إعاقة',
            'relationship_to_member'   => 'صلة القرابة',
            'passport_number'          => 'رقم جواز السفر',
            'can_separate'             => 'يمكن الفصل',
            'can_get_independent'      => 'يمكن الاستقلال',
            'processed_by'             => 'معالج بواسطة',
            'processed_at'             => 'تاريخ المعالجة',
            'requested_by'             => 'مقدم الطلب',
            'related_entity_type'      => 'نوع الكيان المرتبط',
            'related_entity_id'        => 'رقم الكيان المرتبط',
            default                    => $field,
        };
    }

    public static function getStatusLabel(string $status): string
    {
        return match ($status) {
            'active'           => 'نشط',
            'inactive'         => 'غير نشط',
            'potential'        => 'محتمل',
            'under_review'     => 'قيد المراجعة',
            'accepted'         => 'مقبول',
            'rejected'         => 'مرفوض',
            'payment_pending'  => 'في انتظار الدفع',
            'pending_payment'  => 'في انتظار الدفع',
            'pending'          => 'معلق',
            'processing'       => 'قيد المعالجة',
            'completed'        => 'مكتمل',
            'cancelled'        => 'ملغى',
            'frozen'           => 'مجمد',
            'suspended'        => 'موقوف',
            'dropped'          => 'منسحب',
            'expired'          => 'منتهي',
            default            => $status,
        };
    }

    public static function formatFieldValue(string $field, mixed $value): string
    {
        if ($value === null || $value === '') return '—';

        if (in_array($field, ['amount', 'membership_value', 'addition_fee', 'fee_amount', 'discount_amount', 'total_amount', 'down_payment', 'remaining_balance', 'monthly_payment'], true)) {
            return number_format((float) $value, 2) . ' ج.م';
        }
        if ($field === 'status') return self::getStatusLabel((string) $value);
        if ($field === 'gender') return match ((string) $value) { 'male' => 'ذكر', 'female' => 'أنثى', default => (string) $value };
        if (in_array($field, ['is_archived', 'is_voided', 'has_championship', 'disability_documentation', 'can_separate', 'can_get_independent'], true)) {
            return $value ? 'نعم' : 'لا';
        }
        if ($field === 'payment_type') {
            return \App\Modules\Cashier\Services\PaymentRequestService::getPaymentTypeLabel((string) $value);
        }

        return mb_substr((string) $value, 0, 200);
    }
}