using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using UnityEngine;
using UVC.Log;

namespace UVC.Data
{
    /// <summary>
    /// 데이터 객체들을 키-값 쌍으로 관리하는 중앙 저장소입니다.
    /// </summary>
    /// <remarks>
    /// 이 클래스는 싱글톤 패턴으로 구현되어 있어 애플리케이션 전체에서 
    /// 하나의 인스턴스만 존재합니다. IDataObject 인터페이스를 구현하는
    /// 모든 데이터 객체를 저장하고 검색할 수 있습니다.
    /// </remarks>
    public class DataRepository
    {
        #region Singleton
        /// <summary>
        /// DataRepository 싱글톤 인스턴스를 생성하는 지연 초기화 객체입니다.
        /// </summary>
        protected static readonly Lazy<DataRepository> instance = new Lazy<DataRepository>(() => new DataRepository());
        /// <summary>
        /// 외부에서의 인스턴스 생성을 방지하는 보호된 생성자입니다.
        /// </summary>
        protected DataRepository() { }
        /// <summary>
        /// DataRepository의 단일 인스턴스에 대한 접근자입니다.
        /// </summary>
        public static DataRepository Instance { get { return instance.Value; } }
        #endregion

        /// <summary>
        /// 키로 식별되는 데이터 객체를 저장하는 컬렉션입니다.
        /// </summary>
        private Dictionary<string, IDataObject> dataObjects = new Dictionary<string, IDataObject>();

        /// <summary>
        /// 스레드 동기화를 위한 잠금 객체입니다.
        /// </summary>
        private readonly object syncLock = new object();

        /// <summary>
        /// 데이터 업데이트 시 호출될 핸들러 함수들을 저장하는 딕셔너리입니다.
        /// 각 키에 연결된 데이터가 업데이트될 때 해당 핸들러가 호출됩니다.
        /// </summary>
        private Dictionary<string, Action<IDataObject>> dataUpdateHandlers = new Dictionary<string, Action<IDataObject>>();


        /// <summary>
        /// 저장소에 데이터 객체를 추가하거나 기존 객체를 업데이트합니다.
        /// </summary>
        /// <param name="key">데이터 객체를 식별하는 고유 키</param>
        /// <param name="dataObject">저장할 데이터 객체</param>
        /// <param name="updatedDataOnly">true인 경우 업데이트된 속성만 반환, false인 경우 전체 객체 반환</param>
        /// <returns>새로 추가된 객체 또는 업데이트된 기존 객체</returns>
        public IDataObject AddData(string key, IDataObject dataObject, bool updatedDataOnly = true)
        {
            if (string.IsNullOrEmpty(key))
                throw new ArgumentNullException(nameof(key), "키는 null이거나 빈 문자열일 수 없습니다.");

            if (dataObject == null)
                throw new ArgumentNullException(nameof(dataObject), "데이터 객체는 null일 수 없습니다.");

            lock (syncLock)
            {
                if (!dataObjects.ContainsKey(key))
                {
                    dataObject.InitData();
                    dataObjects.Add(key, dataObject);
                    return dataObject;
                }
                else
                {
                    IDataObject obj = dataObjects[key];
                    obj.UpdateDifferent(dataObject);
                    var newDataObject = dataObject;
                    if (updatedDataOnly) newDataObject = obj.GetUpdatedObject();
                    NotifyDataUpdate(key, newDataObject);
                    return newDataObject;
                }
            }
        }

        /// <summary>
        /// 지정된 키에 해당하는 데이터 객체를 저장소에서 제거합니다.
        /// </summary>
        /// <param name="key">제거할 데이터 객체의 키</param>
        public void RemoveData(string key)
        {
            if (string.IsNullOrEmpty(key))
                throw new ArgumentNullException(nameof(key), "키는 null이거나 빈 문자열일 수 없습니다.");

            lock (syncLock)
            {
                if (dataObjects.ContainsKey(key))
                {
                    dataObjects.Remove(key);
                }
            }
        }

        /// <summary>
        /// 지정된 키에 해당하는 데이터 객체를 저장소에서 검색합니다.
        /// </summary>
        /// <param name="key">검색할 데이터 객체의 키</param>
        /// <returns>키가 존재하면 해당 데이터 객체, 존재하지 않으면 null</returns>
        public IDataObject GetData(string key)
        {
            if (string.IsNullOrEmpty(key))
                throw new ArgumentNullException(nameof(key), "키는 null이거나 빈 문자열일 수 없습니다.");

            lock (syncLock)
            {
                if (dataObjects.ContainsKey(key))
                {
                    return dataObjects[key];
                }
                return null;
            }
        }

        /// <summary>
        /// 지정된 키에 대한 데이터 업데이트 핸들러를 추가합니다.
        /// </summary>
        /// <param name="key">데이터 업데이트를 감시할 키</param>
        /// <param name="handler">데이터가 업데이트되었을 때 호출될 콜백 함수</param>
        /// <remarks>
        /// 같은 키에 대해 여러 핸들러를 등록할 수 있습니다.
        /// 핸들러는 해당 키의 데이터가 AddData를 통해 업데이트될 때마다 호출됩니다.
        /// </remarks>
        public void AddDataUpdateHandler(string key, Action<IDataObject> handler)
        {
            if (string.IsNullOrEmpty(key))
                throw new ArgumentNullException(nameof(key), "키는 null이거나 빈 문자열일 수 없습니다.");
            if (handler == null)
                throw new ArgumentNullException(nameof(handler), "핸들러는 null일 수 없습니다.");
            lock (syncLock)
            {
                if (!dataUpdateHandlers.ContainsKey(key))
                {
                    dataUpdateHandlers[key] = handler;
                }
                else
                {
                    dataUpdateHandlers[key] += handler; // 기존 핸들러에 추가
                }
            }
        }

        /// <summary>
        /// 지정된 키에서 데이터 업데이트 핸들러를 제거합니다.
        /// </summary>
        /// <param name="key">핸들러를 제거할 데이터 키</param>
        /// <param name="handler">제거할 핸들러 함수</param>
        /// <remarks>
        /// 지정된 키에 연결된 핸들러가 없거나, 제거하려는 핸들러가 등록되지 않은 경우 아무 작업도 수행하지 않습니다.
        /// 키에 연결된 마지막 핸들러가 제거되면 해당 키는 dataUpdateHandlers 딕셔너리에서 삭제됩니다.
        /// </remarks>
        public void RemoveDataUpdateHandler(string key, Action<IDataObject> handler)
        {
            if (string.IsNullOrEmpty(key))
                throw new ArgumentNullException(nameof(key), "키는 null이거나 빈 문자열일 수 없습니다.");
            if (handler == null)
                throw new ArgumentNullException(nameof(handler), "핸들러는 null일 수 없습니다.");
            lock (syncLock)
            {
                if (dataUpdateHandlers.ContainsKey(key))
                {
                    dataUpdateHandlers[key] -= handler; // 기존 핸들러에서 제거
                    if (dataUpdateHandlers[key] == null)
                    {
                        dataUpdateHandlers.Remove(key); // 핸들러가 없으면 제거
                    }
                }
            }
        }

        /// <summary>
        /// 모든 데이터 업데이트 핸들러를 제거합니다.
        /// </summary>
        /// <remarks>
        /// 이 메서드는 모든 키에 대한 모든 핸들러를 한 번에 제거합니다.
        /// 예를 들어, 씬이 변경되거나 애플리케이션이 종료될 때 
        /// 모든 핸들러를 정리하는 데 유용합니다.
        /// </remarks>
        public void ClearDataUpdateHandlers()
        {
            lock (syncLock)
            {
                dataUpdateHandlers.Clear();
            }
        }

        /// <summary>
        /// 지정된 키의 데이터가 업데이트되었음을 알리고 등록된 핸들러들을 호출합니다.
        /// </summary>
        /// <param name="key">업데이트된 데이터의 키</param>
        /// <param name="dataObject">업데이트된 데이터 객체</param>
        /// <remarks>
        /// 이 메서드는 주로 내부적으로 AddData 메서드에서 호출되어 
        /// 특정 키의 데이터가 변경되었을 때 등록된 핸들러들에게 알립니다.
        /// </remarks>
        private void NotifyDataUpdate(string key, IDataObject dataObject)
        {
            if (string.IsNullOrEmpty(key))
                throw new ArgumentNullException(nameof(key), "키는 null이거나 빈 문자열일 수 없습니다.");
            if (dataObject == null)
                throw new ArgumentNullException(nameof(dataObject), "데이터 객체는 null일 수 없습니다.");
            lock (syncLock)
            {
                if (dataUpdateHandlers.ContainsKey(key))
                {
                    dataUpdateHandlers[key]?.Invoke(dataObject);
                }
            }
        }

        /// <summary>
        /// 저장소에 저장된 모든 데이터 객체의 키 목록을 반환합니다.
        /// </summary>
        /// <returns>저장소에 저장된 모든 데이터 객체의 키 목록</returns>
        public IEnumerable<string> GetAllKeys()
        {
            lock (syncLock)
            {
                return new List<string>(dataObjects.Keys);
            }
        }

        /// <summary>
        /// 저장된 모든 데이터 객체를 반환합니다.
        /// </summary>
        /// <returns>저장소의 모든 데이터 객체</returns>
        public IEnumerable<IDataObject> GetAllData()
        {
            lock (syncLock)
            {
                return new List<IDataObject>(dataObjects.Values);
            }
        }

        /// <summary>
        /// 지정된 키에 대한 데이터 객체가 저장소에 존재하는지 확인합니다.
        /// </summary>
        /// <param name="key">확인할 키</param>
        /// <returns>키가 존재하면 true, 그렇지 않으면 false</returns>
        public bool ContainsKey(string key)
        {
            lock (syncLock)
            {
                return dataObjects.ContainsKey(key);
            }
        }

        /// <summary>
        /// 저장소의 모든 데이터를 삭제합니다.
        /// </summary>
        public void Clear()
        {
            lock (syncLock)
            {
                dataObjects.Clear();
                dataUpdateHandlers.Clear();
            }
        }


        /// <summary>
        /// 지정된 키의 데이터 객체를 특정 타입으로 변환하여 반환합니다.
        /// </summary>
        /// <typeparam name="T">변환할 타입 (IDataObject를 구현해야 함)</typeparam>
        /// <param name="key">검색할 키</param>
        /// <returns>변환된 객체 또는 null</returns>
        public T GetDataAs<T>(string key) where T : class, IDataObject
        {
            lock (syncLock)
            {
                if (dataObjects.ContainsKey(key) && dataObjects[key] is T typedObject)
                {
                    return typedObject;
                }
                return null;
            }
        }

        /// <summary>
        /// 지정된 조건을 만족하는 첫 번째 데이터 객체를 찾습니다.
        /// </summary>
        /// <param name="predicate">데이터 객체를 필터링할 조건</param>
        /// <returns>조건을 만족하는 첫 번째 데이터 객체와 해당 키, 없으면 null</returns>
        public (string Key, IDataObject Data) FindFirst(Func<IDataObject, bool> predicate)
        {
            if (predicate == null)
                throw new ArgumentNullException(nameof(predicate));

            lock (syncLock)
            {
                foreach (var pair in dataObjects)
                {
                    if (predicate(pair.Value))
                    {
                        return (pair.Key, pair.Value);
                    }
                }
                return (null, null);
            }
        }

        /// <summary>
        /// 지정된 조건을 만족하는 모든 데이터 객체를 찾습니다.
        /// </summary>
        /// <param name="predicate">데이터 객체를 필터링할 조건</param>
        /// <returns>조건을 만족하는 모든 데이터 객체와 해당 키</returns>
        public IEnumerable<(string Key, IDataObject Data)> FindAll(Func<IDataObject, bool> predicate)
        {
            if (predicate == null)
                throw new ArgumentNullException(nameof(predicate));

            lock (syncLock)
            {
                List<(string, IDataObject)> result = new List<(string, IDataObject)>();
                foreach (var pair in dataObjects)
                {
                    if (predicate(pair.Value))
                    {
                        result.Add((pair.Key, pair.Value));
                    }
                }
                return result;
            }
        }

        /// <summary>
        /// 저장소의 전체 내용을 JSON 문자열로 직렬화합니다.
        /// </summary>
        /// <returns>저장소 데이터의 JSON 표현</returns>
        public string ToJson()
        {
            JObject result = new JObject();

            lock (syncLock)
            {
                foreach (var pair in dataObjects)
                {
                    if (pair.Value is DataObject dataObject)
                    {
                        result[pair.Key] = JObject.FromObject(dataObject);
                    }
                    else if (pair.Value is DataArray dataArray)
                    {
                        result[pair.Key] = JArray.FromObject(dataArray);
                    }
                }
            }

            return result.ToString();
        }

        /// <summary>
        /// JSON 문자열에서 데이터를 불러와 저장소에 추가합니다.
        /// </summary>
        /// <param name="json">저장소 데이터의 JSON 표현</param>
        public void LoadFromJson(string json)
        {
            if (string.IsNullOrEmpty(json))
                return;

            try
            {
                JObject data = JObject.Parse(json);

                foreach (var property in data.Properties())
                {
                    string key = property.Name;
                    JToken value = property.Value;

                    if (value is JObject jObject)
                    {
                        DataObject dataObject = new DataObject(jObject);
                        AddData(key, dataObject, false);
                    }
                    else if (value is JArray jArray)
                    {
                        DataArray dataArray = new DataArray(jArray);
                        AddData(key, dataArray, false);
                    }
                }
            }
            catch (JsonException ex)
            {
                ULog.Error($"JSON 파싱 오류", ex);
            }
        }

    }
}
