using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using System;
using UnityEngine.UI;
using Postgrest;
using Cysharp.Threading.Tasks;
using UnityEngine.Rendering.Universal;
using System.Globalization;
using Oculus.Interaction;
using System.Reactive;
using Postgrest.Interfaces;

namespace GhostSystem
{
    public class GhostReplayManager : MonoBehaviour
    {
        [Header("Replay Settings")]
        public string sceneId;
        public string UserFilter = null; // optional: filtere nach User-ID
        public bool Loop = true;
        public DateTime selectedStartTime;
        public DateSelector dateSelector;

        [Tooltip("Optional: Begrenze die Frame-Abfrage auf einen Zeitraum ab Startzeit.")]
        public bool LimitTimeRange = true;

        [Tooltip("Zeitraum in Minuten, der ab Startzeit ausgewertet wird.")]
        public int TimeRangeMinutes = 15;

        [Header("Target Objects")]
        public List<TrackedObjectBinding> TrackedObjects;

        [Header("Playback")]
        public float PlaybackSpeed = 1f;
        public bool AutoStart = true;
        public Slider playbackSlider;
        private bool isScrubbing = false;

        private bool isPlaying = false;
        private float playbackTime = 0f;
        private float totalDuration = 0f;
        private string firstTimestamp = "";
        private string lastTimestamp = "";

        private Dictionary<string, List<FrameModel>> objectTracks = new();

        private bool supabaseReady = false;
        private bool sceneIdReady = false;
        private string loadedSceneId;
        private int doorCount;

        private DateTime latestLoadedTimestamp;

        [Serializable]
        public class TrackedObjectBinding
        {
            public string objectId;
            public GameObject targetObject;
        }

        private async void OnEnable()
        {
            SupabaseManager.OnSuperbaseReady += HandleSupabaseReady;
            RoomManager.OnSceneIdReady += HandleSceneIdReady;
        }

        private void OnDisable()
        {
            SupabaseManager.OnSuperbaseReady -= HandleSupabaseReady;
            RoomManager.OnSceneIdReady -= HandleSceneIdReady;
        }

        private void HandleSupabaseReady()
        {
            supabaseReady = true;
            TryInitializeReplay();
        }

        private void HandleSceneIdReady(string id, List<GameObject> doors)
        {
            sceneIdReady = true;
            loadedSceneId = id;
            foreach (GameObject door in doors)
            {
                TrackedObjectBinding trackedObjectBinding = new TrackedObjectBinding();
                trackedObjectBinding.objectId = "Door";
                trackedObjectBinding.targetObject = door.GetComponentInChildren<OneGrabRotateTransformer>().gameObject;
                TrackedObjects.Add(trackedObjectBinding);
                doorCount++;
            }
            TryInitializeReplay();
        }

        private async UniTaskVoid TryInitializeReplay()
        {
            if (!supabaseReady || !sceneIdReady) return;

            sceneId = loadedSceneId;

            await LoadAllFrames();
            StartReplay();
            _ = PeriodicUpdateFrames();
        }

        public async UniTask LoadAllFrames()
        {
            Debug.Log("SceneId used in query: " + sceneId);
            Debug.Log("StartTime filter: " + dateSelector.selectedDateTime().ToString("o"));
            Debug.Log("EndTime filter: " + dateSelector.EndTime().ToString("o"));

            var startTime = dateSelector.selectedDateTime();
            var endTime = dateSelector.EndTime();

            objectTracks.Clear();
            int batchSize = 1000;
            int offset = 0;
            bool more = true;
            List<FrameModel> allFrames = new();

            while (more)
            {
                List<IPostgrestQueryFilter> timestampFilter = new List<IPostgrestQueryFilter>();
                timestampFilter.Add(new QueryFilter("timestamp", Postgrest.Constants.Operator.GreaterThanOrEqual, startTime.ToString("o")));
                timestampFilter.Add(new QueryFilter("timestamp", Postgrest.Constants.Operator.LessThan, endTime.ToString("o")));

                var query = SupabaseManager.instance.supabase
                    .From<FrameModel>()
                    .Filter("scene_id", Postgrest.Constants.Operator.Equals, sceneId)
                    //.Where(x => x.Timestamp >= "timestamp '2025-06-14 10:27:00' and" x => x.Timestamp.timestamp <= "timestamp '2025-06-14 10:29:00'")
                    //.Filter("timestamp", Postgrest.Constants.Operator.GreaterThanOrEqual, startTime.ToString("o"))
                    .And(timestampFilter)
                    //.Filter("timestamp", Postgrest.Constants.Operator.LessThan, endTime.ToString("o"))
                    .Order("timestamp", Postgrest.Constants.Ordering.Ascending)
                    .Limit(batchSize) // Limit, wie viele Frames in einem Batch geladen werden. (Standardmäßig 1000)
                    .Offset(offset); // Um zu wissen, wo der nächste Batch anfängt

                if (!string.IsNullOrEmpty(UserFilter))
                    query = query.Filter("user_id", Postgrest.Constants.Operator.Equals, UserFilter);

                var result = await query.Get();
                if (result.Models.Count == 0) break;
                if (result.Models[0].SceneId != sceneId) break;
                Debug.LogError("Model Count: " + result.Models.Count);
                allFrames.AddRange(result.Models);
                offset += batchSize;
                more = result.Models.Count == batchSize;
            }

            foreach (var frame in allFrames)
            {
                if (!objectTracks.ContainsKey(frame.ObjectId))
                    objectTracks[frame.ObjectId] = new List<FrameModel>();
                objectTracks[frame.ObjectId].Add(frame);
            }

            if (allFrames.Count > 1)
            {
                latestLoadedTimestamp = allFrames.Last().TimestampParsed;
                totalDuration = (float)(latestLoadedTimestamp - allFrames.First().TimestampParsed).TotalSeconds;
                firstTimestamp = allFrames.First().TimestampParsed.ToLongTimeString();
                lastTimestamp = latestLoadedTimestamp.ToLongTimeString();
            }

            Debug.LogWarning($"Loaded {allFrames.Count} frames across {objectTracks.Count} objects.");
        }

        private async UniTaskVoid PeriodicUpdateFrames() // lädt alle 10 s neue Frames nach, falls verfügbar
        {
            while (true)
            {
                await UniTask.Delay(TimeSpan.FromSeconds(10));
                await LoadNewFramesAfter(latestLoadedTimestamp);
            }
        }

        private async UniTask LoadNewFramesAfter(DateTime afterTime) // lädt einen neuen Batch nach.
        {
            var endTime = dateSelector.EndTime();

            List<IPostgrestQueryFilter> timestampFilter = new List<IPostgrestQueryFilter>();
            timestampFilter.Add(new QueryFilter("timestamp", Postgrest.Constants.Operator.GreaterThan, afterTime.ToString("o")));
            timestampFilter.Add(new QueryFilter("timestamp", Postgrest.Constants.Operator.LessThan, endTime.ToString("o")));

            var query = SupabaseManager.instance.supabase
                .From<FrameModel>()
                .Filter("scene_id", Postgrest.Constants.Operator.Equals, sceneId)
                .And(timestampFilter)
                //.Filter("timestamp", Postgrest.Constants.Operator.GreaterThan, afterTime.ToString("o"))
                //.Filter("timestamp", Postgrest.Constants.Operator.LessThanOrEqual, endTime.ToString("o"))
                .Order("timestamp", Postgrest.Constants.Ordering.Ascending);

            if (!string.IsNullOrEmpty(UserFilter))
                query = query.Filter("user_id", Postgrest.Constants.Operator.Equals, UserFilter);

            var result = await query.Get();
            var newFrames = result.Models;

            foreach (var frame in newFrames)
            {
                if (!objectTracks.ContainsKey(frame.ObjectId))
                    objectTracks[frame.ObjectId] = new List<FrameModel>();
                objectTracks[frame.ObjectId].Add(frame);
            }

            if (newFrames.Count > 0)
            {
                latestLoadedTimestamp = newFrames.Last().TimestampParsed;
                totalDuration = (float)(latestLoadedTimestamp - objectTracks.Values.SelectMany(f => f).First().TimestampParsed).TotalSeconds;
            }

            Debug.Log($"Appended {newFrames.Count} new frames.");
        }


        public void StartReplay() // Für das Play UI-Element, um das Replay zu starten
        {
            isPlaying = true;
            playbackTime = 0f;
            Debug.LogError("Replay started: total duration = " + totalDuration + " s, First: " + firstTimestamp + ", Last: " + lastTimestamp);
        }

        public void PauseReplay() => isPlaying = !isPlaying;

        public void StopReplay() // Für das Stop UI-Element, um das Replay zu beenden
        {
            isPlaying = false;
            playbackTime = 0f;
        }

        public void SetPlaybackTime(float time) // Für das Slider UI-Element zum Scrubben
        {
            playbackTime = Mathf.Clamp(time, 0f, totalDuration);
        }

        private void Update()
        {
            if (!isPlaying || objectTracks.Count == 0) return;

            playbackTime += Time.deltaTime * PlaybackSpeed;

            if (playbackTime > totalDuration)
            {
                if (Loop)
                    playbackTime = 0f;
                else
                    isPlaying = false;
            }

            DateTime currentTimestamp = objectTracks.Values
                .SelectMany(list => list)
                .First().TimestampParsed + TimeSpan.FromSeconds(playbackTime);

            foreach (var binding in TrackedObjects)
            {
                if (!objectTracks.ContainsKey(binding.objectId)) continue;

                var frames = objectTracks[binding.objectId];
                if (frames.Count < 2) continue;

                FrameModel a = null, b = null;

                for (int i = 0; i < frames.Count - 1; i++)
                {
                    var tA = frames[i].TimestampParsed;
                    var tB = frames[i + 1].TimestampParsed;

                    if (tA <= currentTimestamp && tB >= currentTimestamp)
                    {
                        a = frames[i];
                        b = frames[i + 1];
                        break;
                    }
                }

                if (a != null && b != null)
                {
                    double tA = a.TimestampParsed.Subtract(DateTime.UnixEpoch).TotalSeconds;
                    double tB = b.TimestampParsed.Subtract(DateTime.UnixEpoch).TotalSeconds;
                    double tCurrent = currentTimestamp.Subtract(DateTime.UnixEpoch).TotalSeconds;

                    float t = Mathf.Clamp01((float)((tCurrent - tA) / (tB - tA)));

                    Vector3 pos = Vector3.Lerp(a.Position, b.Position, t);
                    Quaternion rot = Quaternion.Slerp(a.Rotation, b.Rotation, t);

                    binding.targetObject.transform.SetPositionAndRotation(pos, rot);
                }
            }

            if (!isScrubbing && playbackSlider != null && totalDuration > 0f)
            {
                playbackSlider.value = playbackTime / totalDuration;
            }
        }

        public void SetSliderScrubValue(float sliderValue)
        {
            if (isScrubbing)
                SetPlaybackTime(sliderValue * totalDuration);
        }


        public void OnSliderStartDrag()
        {
            isScrubbing = true;
        }

        public void OnSliderEndDrag()
        {
            isScrubbing = false;
            SetPlaybackTime(playbackSlider.value * totalDuration);
        }

        public void RemoveDoors()
        {
            TrackedObjects.RemoveRange(TrackedObjects.Count - doorCount, doorCount);
            doorCount = 0;
        }

    }
}

