<?php
declare(strict_types=1);

function ensure_audit_log_schema(mysqli $conn): void
{
    static $checked = false;

    if ($checked) {
        return;
    }

    if (runtime_schema_bootstrap_is_current()) {
        $checked = true;

        return;
    }

    $checked = true;

    $conn->query("
        CREATE TABLE IF NOT EXISTS audit_logs (
            audit_log_id INT AUTO_INCREMENT PRIMARY KEY,
            module_key VARCHAR(40) NOT NULL,
            action_key VARCHAR(40) NOT NULL,
            entity_type VARCHAR(40) NOT NULL,
            entity_id INT NULL,
            order_id INT NULL,
            payment_id INT NULL,
            customer_id INT NULL,
            car_id INT NULL,
            actor_employee_id INT NULL,
            actor_name VARCHAR(120) NULL,
            branch_name VARCHAR(120) NULL,
            bill_number VARCHAR(30) NULL,
            amount DECIMAL(10,2) NULL,
            message VARCHAR(255) NULL,
            before_json LONGTEXT NULL,
            after_json LONGTEXT NULL,
            meta_json LONGTEXT NULL,
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            INDEX idx_audit_logs_created_at (created_at),
            INDEX idx_audit_logs_module_key (module_key),
            INDEX idx_audit_logs_action_key (action_key),
            INDEX idx_audit_logs_order_id (order_id),
            INDEX idx_audit_logs_payment_id (payment_id),
            INDEX idx_audit_logs_actor_employee_id (actor_employee_id),
            INDEX idx_audit_logs_branch_name (branch_name),
            INDEX idx_audit_logs_bill_number (bill_number)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
    ");

    $reportIndexResult = $conn->query("SHOW INDEX FROM audit_logs WHERE Key_name = 'idx_audit_logs_report'");
    if ($reportIndexResult instanceof mysqli_result && $reportIndexResult->num_rows === 0) {
        $conn->query("ALTER TABLE audit_logs ADD INDEX idx_audit_logs_report (created_at, module_key, action_key)");
    }

    $branchCreatedIndexResult = $conn->query("SHOW INDEX FROM audit_logs WHERE Key_name = 'idx_audit_logs_branch_created'");
    if ($branchCreatedIndexResult instanceof mysqli_result && $branchCreatedIndexResult->num_rows === 0) {
        $conn->query("ALTER TABLE audit_logs ADD INDEX idx_audit_logs_branch_created (branch_name, created_at)");
    }
}

function audit_normalize_payload($value, int $depth = 0)
{
    if ($depth > 5) {
        return '[truncated depth]';
    }

    if (is_string($value)) {
        $value = trim($value);

        if (function_exists('mb_strlen') && function_exists('mb_substr')) {
            return mb_strlen($value) > 500
                ? mb_substr($value, 0, 500) . '...'
                : $value;
        }

        return strlen($value) > 500
            ? substr($value, 0, 500) . '...'
            : $value;
    }

    if (!is_array($value)) {
        return $value;
    }

    $isSequential = array_keys($value) === range(0, count($value) - 1);
    $limit = $isSequential ? 25 : 40;
    $normalized = array();
    $index = 0;

    foreach ($value as $key => $item) {
        if ($index >= $limit) {
            $normalized['__truncated_items'] = count($value) - $limit;
            break;
        }

        $normalized[$key] = audit_normalize_payload($item, $depth + 1);
        $index++;
    }

    return $normalized;
}

function audit_encode_payload($value): ?string
{
    if ($value === null) {
        return null;
    }

    $encoded = json_encode(audit_normalize_payload($value), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

    return $encoded === false ? null : $encoded;
}

function audit_diff_assoc(array $before, array $after): array
{
    $changedBefore = array();
    $changedAfter = array();
    $keys = array_values(array_unique(array_merge(array_keys($before), array_keys($after))));

    foreach ($keys as $key) {
        $beforeValue = $before[$key] ?? null;
        $afterValue = $after[$key] ?? null;

        if (json_encode($beforeValue, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) === json_encode($afterValue, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)) {
            continue;
        }

        $changedBefore[$key] = $beforeValue;
        $changedAfter[$key] = $afterValue;
    }

    return array(
        'before' => $changedBefore,
        'after' => $changedAfter,
    );
}

function audit_membership_diff(array $before, array $after): array
{
    $beforeValues = array_values(array_unique(array_map('strval', $before)));
    $afterValues = array_values(array_unique(array_map('strval', $after)));

    sort($beforeValues);
    sort($afterValues);

    return array(
        'added' => array_values(array_diff($afterValues, $beforeValues)),
        'removed' => array_values(array_diff($beforeValues, $afterValues)),
    );
}

function audit_log(mysqli $conn, array $entry): void
{
    ensure_audit_log_schema($conn);

    $actorEmployeeId = isset($entry['actor_employee_id']) ? (int) $entry['actor_employee_id'] : 0;
    $actorName = trim((string) ($entry['actor_name'] ?? ''));

    if ($actorEmployeeId <= 0 || $actorName === '') {
        $currentUser = auth_current_user($conn);
        if ($actorEmployeeId <= 0) {
            $actorEmployeeId = (int) ($currentUser['employee_id'] ?? 0);
        }
        if ($actorName === '') {
            $actorName = trim((string) ($currentUser['name'] ?? ''));
        }
    }

    $moduleKey = trim((string) ($entry['module_key'] ?? 'general'));
    $actionKey = trim((string) ($entry['action_key'] ?? 'update'));
    $entityType = trim((string) ($entry['entity_type'] ?? 'record'));
    $entityId = isset($entry['entity_id']) ? (int) $entry['entity_id'] : null;
    $orderId = isset($entry['order_id']) ? (int) $entry['order_id'] : null;
    $paymentId = isset($entry['payment_id']) ? (int) $entry['payment_id'] : null;
    $customerId = isset($entry['customer_id']) ? (int) $entry['customer_id'] : null;
    $carId = isset($entry['car_id']) ? (int) $entry['car_id'] : null;
    $branchName = trim((string) ($entry['branch_name'] ?? ''));
    $billNumber = trim((string) ($entry['bill_number'] ?? ''));
    $amount = isset($entry['amount']) ? (float) $entry['amount'] : null;
    $message = trim((string) ($entry['message'] ?? ''));
    $beforeJson = audit_encode_payload($entry['before'] ?? null);
    $afterJson = audit_encode_payload($entry['after'] ?? null);
    $metaJson = audit_encode_payload($entry['meta'] ?? null);

    if ($moduleKey === '') {
        $moduleKey = 'general';
    }

    if ($actionKey === '') {
        $actionKey = 'update';
    }

    if ($entityType === '') {
        $entityType = 'record';
    }

    if ($actorEmployeeId <= 0) {
        $actorEmployeeId = null;
    }

    if ($actorName === '') {
        $actorName = null;
    }

    if ($branchName === '') {
        $branchName = null;
    }

    if ($billNumber === '') {
        $billNumber = null;
    }

    if ($message === '') {
        $message = null;
    }

    $stmt = db_prepare($conn, "
        INSERT INTO audit_logs (
            module_key,
            action_key,
            entity_type,
            entity_id,
            order_id,
            payment_id,
            customer_id,
            car_id,
            actor_employee_id,
            actor_name,
            branch_name,
            bill_number,
            amount,
            message,
            before_json,
            after_json,
            meta_json
        )
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    ");
    $stmt->bind_param(
        'sssiiiiiisssdssss',
        $moduleKey,
        $actionKey,
        $entityType,
        $entityId,
        $orderId,
        $paymentId,
        $customerId,
        $carId,
        $actorEmployeeId,
        $actorName,
        $branchName,
        $billNumber,
        $amount,
        $message,
        $beforeJson,
        $afterJson,
        $metaJson
    );
    $stmt->execute();
    $stmt->close();
}
