using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Cysharp.Threading.Tasks;
using Newtonsoft.Json;
using System;
using Oculus.Interaction;
using System.IO;

namespace GhostSystem
{
    public class GhostRecorderBatch : MonoBehaviour
    {
        public string SceneId;
        public float SampleRate = 0.05f;
        public int BatchSize = 10;
        public float FlushInterval = 2f;

        public List<Transform> TrackedObjects;
        private int doorCount;

        private float _sampleTimer;
        private float _flushTimer;
        private bool _isRecording;

        private bool supabaseReady = false;
        private bool sceneIdReady = false;

        private readonly List<FrameModel> _frameBuffer = new();

        private string _offlinePath;

        public static event Action OnRecord;
        public static event Action OnStopRecord;
        public static event Action OnUploadFinished;

        /*private void Awake()
        {
            DontDestroyOnLoad(this);
        }*/

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

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

        private void Start()
        {
            _offlinePath = Path.Combine(Application.persistentDataPath, "offline_log.jsonl");
        }

        private void HandleSupabaseReady()
        {
            supabaseReady = true;
        }

        private void HandleSceneIdReady(string id, List<GameObject> doors)
        {
            SceneId = id;
            sceneIdReady = true;
            if (SceneId == "Base")
            {
                //_flushTimer = 0f;
                //_ = FlushBatchAsync(); // Fire and forget
            }
            else
            {
                foreach (GameObject door in doors)
                {
                    var rotator = door.GetComponentInChildren<OneGrabRotateTransformer>();
                    if (rotator != null)
                    {
                        TrackedObjects.Add(rotator.transform);
                        doorCount++;
                    }
                }
            }
        }

        private void Update()
        {
            if (!_isRecording) return;

            _sampleTimer += Time.deltaTime;
            _flushTimer += Time.deltaTime;

            if (_sampleTimer >= SampleRate)
            {
                _sampleTimer = 0f;
                RecordSample();
            }

            // Würde genutzt werden, um live in die Datenbank hochzuladen.
            /*if (_frameBuffer.Count >= BatchSize || _flushTimer >= FlushInterval)
            {
                _flushTimer = 0f;
                _ = FlushBatchAsync(); // Fire and forget
            }*/
        }

        private void RecordSample()
        {
            foreach (var obj in TrackedObjects)
            {
                var frame = new GhostFrame(SceneId, obj.name, obj.position, obj.rotation);
                _frameBuffer.Add(FrameModel.FromGhostFrame(frame));
            }
        }

        private async UniTaskVoid FlushBatchAsync() // Die eigentliche Methode zum Hochladen eines Batches/leeren des Buffers.
        {
            if (_frameBuffer.Count == 0 || !supabaseReady) return;

            var batch = new List<FrameModel>(_frameBuffer);
            _frameBuffer.Clear();
            Debug.Log("Flushing Frame Buffer");

            try
            {
                var response = await SupabaseManager.instance.supabase.From<FrameModel>().Insert(batch); // Versucht, den Batch an Superbase hochzuladen
                if (!response.ResponseMessage.IsSuccessStatusCode)
                {
                    Debug.LogWarning($"Batch insert failed: {response.ResponseMessage.StatusCode}, fallback to offline.");
                    await FallbackToOfflineAsync(batch);
                }
                if (response.ResponseMessage.IsSuccessStatusCode && sceneIdReady) // Löst Action aus, wenn der Upload geklappt hat und die neue Szene geladen ist.
                {
                    OnUploadFinished?.Invoke();
                    Debug.Log("Upload Finished");
                }
                
            }
            catch (Exception ex)
            {
                Debug.LogWarning($"Insert failed, fallback to offline: {ex.Message}");
                await FallbackToOfflineAsync(batch); // Bei nicht erfolgtem Upload wird der Batch lokal gescpeichert.
                OnUploadFinished?.Invoke();          // Die Action für das Ende des Ladescreens wird trotzdem ausgelöst.
                Debug.Log("Upload Finished");
            }
        }

        private async UniTask FallbackToOfflineAsync(List<FrameModel> batch) // Schreibt den Buffer in eine Datei, falls Upload nicht möglich
        {
            try
            {
                using var writer = new StreamWriter(_offlinePath, append: true);
                foreach (var model in batch)
                {
                    string jsonLine = JsonConvert.SerializeObject(model);
                    await writer.WriteLineAsync(jsonLine);
                }
            }
            catch (Exception ex)
            {
                Debug.LogError($"Offline write failed: {ex.Message}");
            }
        }

        public void StartRecording()
        {
            if (!supabaseReady || !sceneIdReady) return;
            _isRecording = true;
            OnRecord?.Invoke();
            Debug.Log($"Recording {SceneId}");
        }

        public void StopRecording()
        {
            _isRecording = false;
            sceneIdReady = false;
            OnStopRecord?.Invoke();
            Debug.Log($"Stop recording: {SceneId}");
            
            if (doorCount > 0 && TrackedObjects.Count >= doorCount)
            {
                TrackedObjects.RemoveRange(TrackedObjects.Count - doorCount, doorCount);
            }

            doorCount = 0;
        }

        public void Flush() // Manueller Flush
        {
            _flushTimer = 0f;
            _ = FlushBatchAsync(); // Fire and forget
        }
    }
}

