<?php

/**
 * ✅ MotionTracker: طبقة تحليل لحظية لتجميع التوقفات والحركة وإزالة النويز
 */
class MotionTracker
{
    private const STOP_RADIUS_METERS = 30;           // نصف قطر يسمح باعتبار الجهاز متوقفاً
    private const STOP_MIN_DURATION_SECONDS = 300;   // 5 دقائق
    private const MIN_MOVE_DISTANCE_METERS = 120;    // تجاهل التحركات القصيرة جداً
    private const NOISE_JUMP_METERS = 1500;          // قفزة مفاجئة تعتبر نويز
    private const MAX_REALISTIC_SPEED_KMH = 180;     // أي سرعة أعلى تعتبر بيانات خاطئة

    private static $booted = false;

    public static function boot(PDO $conn): void
    {
        if (self::$booted) {
            return;
        }

        self::ensureTables($conn);
        self::$booted = true;
    }

    public static function handlePoint(PDO $conn, array $point): void
    {
        if (empty($point['device_id']) || !isset($point['latitude'], $point['longitude'])) {
            return;
        }

        $point['timestamp'] = normalize_incoming_timestamp($point['timestamp'] ?? null);
        $point['latitude'] = (float)$point['latitude'];
        $point['longitude'] = (float)$point['longitude'];

        $state = self::fetchState($conn, $point['device_id']);
        if (!$state) {
            $state = self::initialState($point);
            self::persistState($conn, $state);
            return;
        }

        self::processPoint($conn, $state, $point);
    }

    private static function ensureTables(PDO $conn): void
    {
        $conn->exec("
            CREATE TABLE IF NOT EXISTS device_motion_state (
                device_id VARCHAR(191) PRIMARY KEY,
                device_name VARCHAR(191) NULL,
                current_state ENUM('idle','moving','stopped') NOT NULL DEFAULT 'idle',
                last_latitude DECIMAL(10,7) NULL,
                last_longitude DECIMAL(10,7) NULL,
                last_timestamp DATETIME NULL,
                segment_start_time DATETIME NULL,
                segment_start_latitude DECIMAL(10,7) NULL,
                segment_start_longitude DECIMAL(10,7) NULL,
                segment_points INT UNSIGNED NOT NULL DEFAULT 0,
                segment_distance DOUBLE NOT NULL DEFAULT 0,
                stationary_duration INT UNSIGNED NOT NULL DEFAULT 0,
                pending_stop_start_time DATETIME NULL,
                pending_stop_latitude DECIMAL(10,7) NULL,
                pending_stop_longitude DECIMAL(10,7) NULL,
                noise_points INT UNSIGNED NOT NULL DEFAULT 0,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
        ");

        $conn->exec("
            CREATE TABLE IF NOT EXISTS device_motion_events (
                id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
                device_id VARCHAR(191) NOT NULL,
                device_name VARCHAR(191) NULL,
                event_type ENUM('stop','move') NOT NULL,
                start_time DATETIME NOT NULL,
                end_time DATETIME NOT NULL,
                start_latitude DECIMAL(10,7) NULL,
                start_longitude DECIMAL(10,7) NULL,
                end_latitude DECIMAL(10,7) NULL,
                end_longitude DECIMAL(10,7) NULL,
                duration_seconds INT UNSIGNED NOT NULL,
                distance_meters DOUBLE NOT NULL DEFAULT 0,
                points_count INT UNSIGNED NOT NULL DEFAULT 0,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                INDEX idx_device_time (device_id, start_time),
                INDEX idx_event_type (event_type)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
        ");
    }

    private static function fetchState(PDO $conn, string $deviceId): ?array
    {
        $stmt = $conn->prepare("SELECT * FROM device_motion_state WHERE device_id = :device_id LIMIT 1");
        $stmt->execute([':device_id' => $deviceId]);
        $state = $stmt->fetch(PDO::FETCH_ASSOC);

        return $state ?: null;
    }

    private static function initialState(array $point): array
    {
        return [
            'device_id' => $point['device_id'],
            'device_name' => $point['device_name'] ?? null,
            'current_state' => 'idle',
            'last_latitude' => $point['latitude'],
            'last_longitude' => $point['longitude'],
            'last_timestamp' => $point['timestamp'],
            'segment_start_time' => $point['timestamp'],
            'segment_start_latitude' => $point['latitude'],
            'segment_start_longitude' => $point['longitude'],
            'segment_points' => 1,
            'segment_distance' => 0,
            'stationary_duration' => 0,
            'pending_stop_start_time' => null,
            'pending_stop_latitude' => null,
            'pending_stop_longitude' => null,
            'noise_points' => 0
        ];
    }

    private static function processPoint(PDO $conn, array $state, array $point): void
    {
        $currentTs = strtotime($point['timestamp']);
        $lastTs = $state['last_timestamp'] ? strtotime($state['last_timestamp']) : null;
        if ($lastTs !== null && $currentTs !== false && $currentTs < $lastTs - 5) {
            // تجاهل النقاط القديمة جداً (أقل من آخر زمن بست ثواني) حتى لا نفقد الاتساق
            return;
        }

        $timeDelta = ($lastTs && $currentTs) ? max(0, $currentTs - $lastTs) : 0;
        $distanceFromLast = (isset($state['last_latitude'], $state['last_longitude']))
            ? self::haversine((float)$state['last_latitude'], (float)$state['last_longitude'], $point['latitude'], $point['longitude'])
            : 0.0;

        if (self::isNoise($point, $distanceFromLast, $timeDelta)) {
            $state['noise_points'] = (int)$state['noise_points'] + 1;
            self::persistState($conn, $state);
            return;
        }

        $state['device_name'] = $point['device_name'] ?? $state['device_name'];

        $isWithinStopRadius = $distanceFromLast <= self::STOP_RADIUS_METERS;
        if ($isWithinStopRadius) {
            if (empty($state['pending_stop_start_time'])) {
                $state['pending_stop_start_time'] = $state['last_timestamp'] ?? $point['timestamp'];
                $state['pending_stop_latitude'] = $state['last_latitude'] ?? $point['latitude'];
                $state['pending_stop_longitude'] = $state['last_longitude'] ?? $point['longitude'];
                $state['stationary_duration'] = 0;
            }
            $state['stationary_duration'] += $timeDelta;

            if ($state['stationary_duration'] >= self::STOP_MIN_DURATION_SECONDS && $state['current_state'] !== 'stopped') {
                if ($state['current_state'] === 'moving' && $state['segment_distance'] >= self::MIN_MOVE_DISTANCE_METERS) {
                    self::recordMoveEvent($conn, $state);
                }

                $state['current_state'] = 'stopped';
                $state['segment_start_time'] = $state['pending_stop_start_time'] ?? $point['timestamp'];
                $state['segment_start_latitude'] = $state['pending_stop_latitude'] ?? $point['latitude'];
                $state['segment_start_longitude'] = $state['pending_stop_longitude'] ?? $point['longitude'];
                $state['segment_distance'] = 0;
                $state['segment_points'] = max(1, (int)$state['segment_points']);
            }

            if ($state['current_state'] === 'stopped') {
                $state['segment_points'] = (int)$state['segment_points'] + 1;
            }
        } else {
            // ترك الجهاز لمنطقة التوقف
            if ($state['current_state'] === 'stopped' && !empty($state['segment_start_time'])) {
                self::recordStopEvent($conn, $state);
            }

            $state['pending_stop_start_time'] = null;
            $state['pending_stop_latitude'] = null;
            $state['pending_stop_longitude'] = null;
            $state['stationary_duration'] = 0;

            if ($state['current_state'] !== 'moving') {
                $state['current_state'] = 'moving';
                $state['segment_start_time'] = $state['last_timestamp'] ?? $point['timestamp'];
                $state['segment_start_latitude'] = $state['last_latitude'] ?? $point['latitude'];
                $state['segment_start_longitude'] = $state['last_longitude'] ?? $point['longitude'];
                $state['segment_distance'] = 0;
                $state['segment_points'] = 1;
            }

            $state['segment_distance'] += $distanceFromLast;
            $state['segment_points'] = (int)$state['segment_points'] + 1;
        }

        $state['last_latitude'] = $point['latitude'];
        $state['last_longitude'] = $point['longitude'];
        $state['last_timestamp'] = $point['timestamp'];

        self::persistState($conn, $state);
    }

    private static function recordStopEvent(PDO $conn, array &$state): void
    {
        if (empty($state['segment_start_time']) || empty($state['last_timestamp'])) {
            return;
        }

        $start = strtotime($state['segment_start_time']);
        $end = strtotime($state['last_timestamp']);
        if ($start === false || $end === false) {
            return;
        }

        $duration = max(0, $end - $start);
        if ($duration < self::STOP_MIN_DURATION_SECONDS) {
            return;
        }

        self::insertEvent($conn, [
            'device_id' => $state['device_id'],
            'device_name' => $state['device_name'],
            'event_type' => 'stop',
            'start_time' => $state['segment_start_time'],
            'end_time' => $state['last_timestamp'],
            'start_latitude' => $state['segment_start_latitude'],
            'start_longitude' => $state['segment_start_longitude'],
            'end_latitude' => $state['last_latitude'],
            'end_longitude' => $state['last_longitude'],
            'duration_seconds' => $duration,
            'distance_meters' => $state['segment_distance'],
            'points_count' => max(1, (int)$state['segment_points'])
        ]);

        // إعادة تهيئة بيانات التوقف
        $state['segment_distance'] = 0;
        $state['segment_points'] = 0;
        $state['segment_start_time'] = null;
        $state['pending_stop_start_time'] = null;
        $state['pending_stop_latitude'] = null;
        $state['pending_stop_longitude'] = null;
    }

    private static function recordMoveEvent(PDO $conn, array &$state): void
    {
        if (empty($state['segment_start_time']) || empty($state['last_timestamp'])) {
            return;
        }

        $start = strtotime($state['segment_start_time']);
        $end = strtotime($state['last_timestamp']);
        if ($start === false || $end === false) {
            return;
        }

        $duration = max(0, $end - $start);
        if ($state['segment_distance'] < self::MIN_MOVE_DISTANCE_METERS) {
            return;
        }

        self::insertEvent($conn, [
            'device_id' => $state['device_id'],
            'device_name' => $state['device_name'],
            'event_type' => 'move',
            'start_time' => $state['segment_start_time'],
            'end_time' => $state['last_timestamp'],
            'start_latitude' => $state['segment_start_latitude'],
            'start_longitude' => $state['segment_start_longitude'],
            'end_latitude' => $state['last_latitude'],
            'end_longitude' => $state['last_longitude'],
            'duration_seconds' => $duration,
            'distance_meters' => $state['segment_distance'],
            'points_count' => max(1, (int)$state['segment_points'])
        ]);
    }

    private static function insertEvent(PDO $conn, array $event): void
    {
        $stmt = $conn->prepare("
            INSERT INTO device_motion_events (
                device_id, device_name, event_type, start_time, end_time,
                start_latitude, start_longitude, end_latitude, end_longitude,
                duration_seconds, distance_meters, points_count
            ) VALUES (
                :device_id, :device_name, :event_type, :start_time, :end_time,
                :start_latitude, :start_longitude, :end_latitude, :end_longitude,
                :duration_seconds, :distance_meters, :points_count
            )
        ");

        $stmt->execute([
            ':device_id' => $event['device_id'],
            ':device_name' => $event['device_name'],
            ':event_type' => $event['event_type'],
            ':start_time' => $event['start_time'],
            ':end_time' => $event['end_time'],
            ':start_latitude' => $event['start_latitude'],
            ':start_longitude' => $event['start_longitude'],
            ':end_latitude' => $event['end_latitude'],
            ':end_longitude' => $event['end_longitude'],
            ':duration_seconds' => $event['duration_seconds'],
            ':distance_meters' => $event['distance_meters'],
            ':points_count' => $event['points_count']
        ]);
    }

    private static function persistState(PDO $conn, array $state): void
    {
        $stmt = $conn->prepare("
            INSERT INTO device_motion_state (
                device_id, device_name, current_state, last_latitude, last_longitude, last_timestamp,
                segment_start_time, segment_start_latitude, segment_start_longitude,
                segment_points, segment_distance, stationary_duration,
                pending_stop_start_time, pending_stop_latitude, pending_stop_longitude, noise_points
            ) VALUES (
                :device_id, :device_name, :current_state, :last_latitude, :last_longitude, :last_timestamp,
                :segment_start_time, :segment_start_latitude, :segment_start_longitude,
                :segment_points, :segment_distance, :stationary_duration,
                :pending_stop_start_time, :pending_stop_latitude, :pending_stop_longitude, :noise_points
            )
            ON DUPLICATE KEY UPDATE
                device_name = VALUES(device_name),
                current_state = VALUES(current_state),
                last_latitude = VALUES(last_latitude),
                last_longitude = VALUES(last_longitude),
                last_timestamp = VALUES(last_timestamp),
                segment_start_time = VALUES(segment_start_time),
                segment_start_latitude = VALUES(segment_start_latitude),
                segment_start_longitude = VALUES(segment_start_longitude),
                segment_points = VALUES(segment_points),
                segment_distance = VALUES(segment_distance),
                stationary_duration = VALUES(stationary_duration),
                pending_stop_start_time = VALUES(pending_stop_start_time),
                pending_stop_latitude = VALUES(pending_stop_latitude),
                pending_stop_longitude = VALUES(pending_stop_longitude),
                noise_points = VALUES(noise_points)
        ");

        $stmt->execute([
            ':device_id' => $state['device_id'],
            ':device_name' => $state['device_name'],
            ':current_state' => $state['current_state'],
            ':last_latitude' => $state['last_latitude'],
            ':last_longitude' => $state['last_longitude'],
            ':last_timestamp' => $state['last_timestamp'],
            ':segment_start_time' => $state['segment_start_time'],
            ':segment_start_latitude' => $state['segment_start_latitude'],
            ':segment_start_longitude' => $state['segment_start_longitude'],
            ':segment_points' => $state['segment_points'],
            ':segment_distance' => $state['segment_distance'],
            ':stationary_duration' => $state['stationary_duration'],
            ':pending_stop_start_time' => $state['pending_stop_start_time'],
            ':pending_stop_latitude' => $state['pending_stop_latitude'],
            ':pending_stop_longitude' => $state['pending_stop_longitude'],
            ':noise_points' => $state['noise_points']
        ]);
    }

    private static function isNoise(array $point, float $distance, int $timeDelta): bool
    {
        if (!empty($point['accuracy']) && (float)$point['accuracy'] > 100) {
            return true;
        }

        if ($timeDelta === 0 && $distance > self::NOISE_JUMP_METERS) {
            return true;
        }

        if ($timeDelta > 0) {
            $speedKmh = ($distance / $timeDelta) * 3.6;
            if ($speedKmh > self::MAX_REALISTIC_SPEED_KMH) {
                return true;
            }
        }

        return false;
    }

    private static function haversine(float $lat1, float $lon1, float $lat2, float $lon2): float
    {
        $earthRadius = 6371000; // meters

        $dLat = deg2rad($lat2 - $lat1);
        $dLon = deg2rad($lon2 - $lon1);

        $a = sin($dLat / 2) * sin($dLat / 2) +
            cos(deg2rad($lat1)) * cos(deg2rad($lat2)) *
            sin($dLon / 2) * sin($dLon / 2);
        $c = 2 * atan2(sqrt($a), sqrt(1 - $a));

        return $earthRadius * $c;
    }
}

