<?php
/**
 * Klasa AuditLog - rejestrowanie historii zmian
 */

class AuditLog
{
    private Database $db;
    
    // Typy encji
    const ENTITY_USER = 'user';
    const ENTITY_DEVICE = 'device';
    const ENTITY_PLAYLIST = 'playlist';
    const ENTITY_SETTINGS = 'settings';
    
    // Akcje
    const ACTION_CREATE = 'create';
    const ACTION_UPDATE = 'update';
    const ACTION_DELETE = 'delete';
    const ACTION_RESTORE = 'restore';
    const ACTION_CLONE = 'clone';
    const ACTION_IMPORT = 'import';
    const ACTION_EXPORT = 'export';
    const ACTION_BLOCK = 'block';
    const ACTION_UNBLOCK = 'unblock';
    const ACTION_ACTIVATE = 'activate';
    const ACTION_DEACTIVATE = 'deactivate';
    
    public function __construct()
    {
        $this->db = Database::getInstance();
    }
    
    /**
     * Zapisz wpis do audit log
     */
    public function log(
        string $entityType,
        int $entityId,
        string $action,
        ?array $oldValues = null,
        ?array $newValues = null,
        ?int $userId = null
    ): int {
        // Pobierz user_id z Auth jeśli nie przekazano
        if ($userId === null) {
            $auth = new Auth();
            $user = $auth->user();
            $userId = $user['id'] ?? null;
        }
        
        // Usuń wrażliwe dane
        $oldValues = $this->sanitizeValues($oldValues);
        $newValues = $this->sanitizeValues($newValues);
        
        // Dla update - zapisz tylko zmienione pola
        if ($action === self::ACTION_UPDATE && $oldValues && $newValues) {
            $changes = $this->getChangedFields($oldValues, $newValues);
            if (!empty($changes)) {
                $oldValues = $changes['old'];
                $newValues = $changes['new'];
            }
        }
        
        try {
            return $this->db->insert(
                "INSERT INTO audit_log (user_id, entity_type, entity_id, action, old_values, new_values, ip_address, user_agent, created_at)
                 VALUES (:user_id, :entity_type, :entity_id, :action, :old_values, :new_values, :ip_address, :user_agent, datetime('now'))",
                [
                    'user_id' => $userId,
                    'entity_type' => $entityType,
                    'entity_id' => $entityId,
                    'action' => $action,
                    'old_values' => $oldValues ? json_encode($oldValues, JSON_UNESCAPED_UNICODE) : null,
                    'new_values' => $newValues ? json_encode($newValues, JSON_UNESCAPED_UNICODE) : null,
                    'ip_address' => $_SERVER['REMOTE_ADDR'] ?? null,
                    'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? null
                ]
            );
        } catch (Exception $e) {
            // Loguj błąd do pliku jeśli nie można zapisać do bazy
            if (defined('LOGS_PATH') && is_dir(LOGS_PATH)) {
                $entry = sprintf(
                    "[%s] [AUDIT_ERROR] %s - %s/%d/%s\n",
                    date('Y-m-d H:i:s'),
                    $e->getMessage(),
                    $entityType,
                    $entityId,
                    $action
                );
                @file_put_contents(LOGS_PATH . '/app.log', $entry, FILE_APPEND | LOCK_EX);
            }
            return 0;
        }
    }
    
    /**
     * Pobierz tylko zmienione pola (dla update)
     */
    private function getChangedFields(array $old, array $new): array
    {
        $changedOld = [];
        $changedNew = [];
        
        // Wszystkie klucze
        $allKeys = array_unique(array_merge(array_keys($old), array_keys($new)));
        
        foreach ($allKeys as $key) {
            $oldVal = $old[$key] ?? null;
            $newVal = $new[$key] ?? null;
            
            // Porównaj wartości (z konwersją typów dla porównania)
            if ($oldVal !== $newVal && (string)$oldVal !== (string)$newVal) {
                $changedOld[$key] = $oldVal;
                $changedNew[$key] = $newVal;
            }
        }
        
        return ['old' => $changedOld, 'new' => $changedNew];
    }
    
    /**
     * Usuń wrażliwe dane z wartości
     */
    private function sanitizeValues(?array $values): ?array
    {
        if (!$values) return null;
        
        // Ukrywaj tylko hasła systemowe i tokeny, NIE hasła playlist
        $sensitiveKeys = ['password_hash', 'token', 'secret', 'api_key', 'smtp_pass'];
        
        foreach ($sensitiveKeys as $key) {
            if (isset($values[$key])) {
                $values[$key] = '***HIDDEN***';
            }
        }
        
        return $values;
    }
    
    /**
     * Pobierz historię zmian dla encji
     */
    public function getByEntity(string $entityType, int $entityId, int $limit = 50): array
    {
        return $this->db->fetchAll(
            "SELECT a.*, u.username
             FROM audit_log a
             LEFT JOIN users u ON a.user_id = u.id
             WHERE a.entity_type = :entity_type AND a.entity_id = :entity_id
             ORDER BY a.created_at DESC
             LIMIT :limit",
            ['entity_type' => $entityType, 'entity_id' => $entityId, 'limit' => $limit]
        );
    }
    
    /**
     * Pobierz wszystkie wpisy (dla admina)
     */
    public function getAll(int $limit = 100, int $offset = 0, ?array $filters = null): array
    {
        $where = [];
        $params = ['limit' => $limit, 'offset' => $offset];
        
        if (!empty($filters['entity_type'])) {
            $where[] = "a.entity_type = :entity_type";
            $params['entity_type'] = $filters['entity_type'];
        }
        
        if (!empty($filters['action'])) {
            $where[] = "a.action = :action";
            $params['action'] = $filters['action'];
        }
        
        if (!empty($filters['user_id'])) {
            $where[] = "a.user_id = :user_id";
            $params['user_id'] = $filters['user_id'];
        }
        
        if (!empty($filters['date_from'])) {
            $where[] = "a.created_at >= :date_from";
            $params['date_from'] = $filters['date_from'];
        }
        
        if (!empty($filters['date_to'])) {
            $where[] = "a.created_at <= :date_to";
            $params['date_to'] = $filters['date_to'];
        }
        
        $whereClause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
        
        return $this->db->fetchAll(
            "SELECT a.*, u.username,
                    CASE a.entity_type
                        WHEN 'user' THEN (SELECT username FROM users WHERE id = a.entity_id)
                        WHEN 'device' THEN (SELECT COALESCE(name, mac) FROM devices WHERE id = a.entity_id)
                        WHEN 'playlist' THEN (SELECT name FROM playlists WHERE id = a.entity_id)
                        WHEN 'note' THEN 'Notatka #' || a.entity_id
                        WHEN 'settings' THEN 'Ustawienia systemu'
                        WHEN 'auth' THEN COALESCE((SELECT username FROM users WHERE id = a.entity_id), 'Sesja')
                        WHEN 'admin' THEN 'Operacja administracyjna'
                        ELSE NULL
                    END as entity_name
             FROM audit_log a
             LEFT JOIN users u ON a.user_id = u.id
             {$whereClause}
             ORDER BY a.created_at DESC
             LIMIT :limit OFFSET :offset",
            $params
        );
    }
    
    /**
     * Zlicz wpisy
     */
    public function count(?array $filters = null): int
    {
        $where = [];
        $params = [];
        
        if (!empty($filters['entity_type'])) {
            $where[] = "entity_type = :entity_type";
            $params['entity_type'] = $filters['entity_type'];
        }
        
        if (!empty($filters['action'])) {
            $where[] = "action = :action";
            $params['action'] = $filters['action'];
        }
        
        if (!empty($filters['user_id'])) {
            $where[] = "user_id = :user_id";
            $params['user_id'] = $filters['user_id'];
        }
        
        $whereClause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
        
        $result = $this->db->fetchOne(
            "SELECT COUNT(*) as count FROM audit_log {$whereClause}",
            $params
        );
        
        return $result['count'] ?? 0;
    }
    
    /**
     * Pobierz statystyki aktywności użytkownika
     */
    public function getUserActivity(int $userId, int $days = 30): array
    {
        return $this->db->fetchAll(
            "SELECT DATE(created_at) as date, 
                    entity_type, 
                    action,
                    COUNT(*) as count
             FROM audit_log
             WHERE user_id = :user_id
               AND created_at >= datetime('now', '-' || :days || ' days')
             GROUP BY DATE(created_at), entity_type, action
             ORDER BY date DESC",
            ['user_id' => $userId, 'days' => $days]
        );
    }
    
    /**
     * Pobierz podsumowanie aktywności
     */
    public function getActivitySummary(int $days = 30): array
    {
        return $this->db->fetchAll(
            "SELECT u.id, u.username, u.role,
                    COUNT(*) as total_actions,
                    SUM(CASE WHEN a.action = 'create' THEN 1 ELSE 0 END) as creates,
                    SUM(CASE WHEN a.action = 'update' THEN 1 ELSE 0 END) as updates,
                    SUM(CASE WHEN a.action = 'delete' THEN 1 ELSE 0 END) as deletes,
                    MAX(a.created_at) as last_action
             FROM users u
             LEFT JOIN audit_log a ON u.id = a.user_id 
                AND a.created_at >= datetime('now', '-' || :days || ' days')
             GROUP BY u.id
             ORDER BY total_actions DESC",
            ['days' => $days]
        );
    }
    
    /**
     * Usuń stare wpisy
     */
    public function cleanup(int $daysToKeep = 90): int
    {
        $this->db->execute(
            "DELETE FROM audit_log WHERE created_at < datetime('now', '-' || :days || ' days')",
            ['days' => $daysToKeep]
        );
        
        return $this->db->getLastStatement()->rowCount();
    }
    
    /**
     * Porównaj różnice między wartościami
     */
    public static function diff(?array $old, ?array $new): array
    {
        if (!$old && !$new) return [];
        if (!$old) return ['added' => $new];
        if (!$new) return ['removed' => $old];
        
        $diff = [
            'changed' => [],
            'added' => [],
            'removed' => []
        ];
        
        foreach ($new as $key => $value) {
            if (!array_key_exists($key, $old)) {
                $diff['added'][$key] = $value;
            } elseif ($old[$key] !== $value) {
                $diff['changed'][$key] = [
                    'from' => $old[$key],
                    'to' => $value
                ];
            }
        }
        
        foreach ($old as $key => $value) {
            if (!array_key_exists($key, $new)) {
                $diff['removed'][$key] = $value;
            }
        }
        
        return array_filter($diff);
    }
}
