using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UVC.Json;
using UVC.Log;

namespace UVC.Locale
{
    /// <summary>
    /// 다국어 데이터를 관리하고, 현재 설정된 언어에 맞는 번역 문자열을 제공하는 클래스입니다.
    /// JSON 파일을 로드하여 다국어 데이터를 초기화하며, 언어 변경 시 이벤트를 통해 알림을 제공합니다.
    /// 이 클래스는 싱글톤으로 구현되어 어디서든 <see cref="Instance"/>를 통해 접근할 수 있습니다.
    /// </summary>
    /// <example>
    /// <code>
    /// // 1. LocalizationManager 인스턴스 접근 (싱글톤)
    /// // LocalizationManager locManager = LocalizationManager.Instance; // 직접 인스턴스화 대신 Instance 사용
    ///
    /// // 2. (선택 사항) 언어 변경 이벤트 구독
    /// LocalizationManager.Instance.OnLanguageChanged += (newLanguage) => {
    ///     ULog.Debug($"언어가 {newLanguage}(으)로 변경되었습니다.");
    ///     // UI 업데이트 로직 등
    /// };
    ///
    /// // 3. 다국어 데이터 로드 (StreamingAssets 폴더의 locale.json 파일을 기본으로 로드)
    /// // 인스턴스 생성 시 자동으로 로드하거나, 별도의 초기화 메서드를 통해 로드할 수 있습니다.
    /// // 예시: 로드 메서드가 private 생성자 또는 별도 초기화 메서드에서 호출된다고 가정
    /// if (LocalizationManager.Instance.LoadDefaultLocalizationData()) // LoadDefaultLocalizationData가 public이라고 가정
    /// {
    ///     ULog.Debug("다국어 데이터 로드 성공!");
    ///
    ///     // 4. 사용 가능한 언어 목록 확인 (언어 코드 기준)
    ///     List<string> availableLangCodes = LocalizationManager.Instance.GetAvailableLanguageCodes();
    ///     ULog.Debug("사용 가능한 언어 코드: " + string.Join(", ", availableLangCodes));
    ///     
    ///     // 특정 언어 코드의 표시 이름 가져오기
    ///     string koreanDisplayName = LocalizationManager.Instance.GetLanguageDisplayName("ko-KR");
    ///     ULog.Debug($"ko-KR display name: {koreanDisplayName}"); // 출력: 한국어
    ///
    ///     // 5. 언어 변경
    ///     LocalizationManager.Instance.SetCurrentLanguage("ko-KR"); // 한국어로 변경
    ///
    ///     // 6. 번역된 문자열 가져오기
    ///     string errorMsg = LocalizationManager.Instance.GetString("error"); 
    ///     ULog.Debug(errorMsg); // 현재 언어가 "ko-KR"이면 "에러" 출력
    ///     
    ///     LocalizationManager.Instance.SetCurrentLanguage("en-US"); // 영어로 변경
    ///     welcomeMsg = LocalizationManager.Instance.GetString("welcome_message");
    ///     ULog.Debug(welcomeMsg); // 출력 (영어): Welcome!
    /// }
    /// else
    /// {
    ///     ULog.Error("다국어 데이터 로드 실패.");
    /// }
    /// </code>
    /// </example>
    public class LocalizationManager
    {
        #region singleton

        // 싱글톤 인스턴스를 저장하기 위한 private static 변수
        private static LocalizationManager _instance;
        // 멀티스레드 환경에서의 동시 접근을 제어하기 위한 lock 객체
        private static readonly object _lock = new object();

        /// <summary>
        /// <see cref="LocalizationManager"/>의 싱글톤 인스턴스를 가져옵니다.
        /// 인스턴스가 아직 생성되지 않았다면 새로 생성하여 반환합니다.
        /// </summary>
        public static LocalizationManager Instance
        {
            get
            {
                // Double-checked locking으로 스레드 안전성 확보
                if (_instance == null)
                {
                    lock (_lock)
                    {
                        if (_instance == null)
                        {
                            _instance = new LocalizationManager("ko-KR");
                            _instance.LoadDefaultLocalizationData(); // 기본 언어 데이터 로드
                        }
                    }
                }
                return _instance;
            }
        }

        #endregion


        private LocalizationDataSource _dataSource;
        private string _currentLanguage;

        /// <summary>
        /// 언어가 변경되었을 때 발생하는 이벤트의 델리게이트입니다.
        /// </summary>
        /// <param name="newLanguageCode">새롭게 설정된 언어 코드입니다 (예: "en-US", "ko-KR").</param>
        public delegate void LanguageChangedHandler(string newLanguageCode);
        /// <summary>
        /// 현재 언어가 <see cref="SetCurrentLanguage"/> 메서드를 통해 성공적으로 변경되었을 때 발생하는 이벤트입니다.
        /// </summary>
        public event LanguageChangedHandler OnLanguageChanged;

        /// <summary>
        /// 현재 설정된 언어 코드를 가져옵니다.
        /// </summary>
        public string CurrentLanguage => _currentLanguage;

        /// <summary>
        /// 로드된 다국어 데이터에서 사용 가능한 모든 언어 코드 목록을 가져옵니다.
        /// 데이터가 로드되지 않았거나 번역 데이터가 비어있으면 빈 리스트를 반환합니다.
        /// </summary>
        public List<string> AvailableLanguages
        {
            get
            {
                if (_dataSource?.Languages != null && _dataSource.Languages.Count > 0)
                {
                    return new List<string>(_dataSource.Languages.Keys);
                }
                return new List<string>();
            }
        }

        /// <summary>
        /// 지정된 언어 코드에 해당하는 표시 이름을 가져옵니다.
        /// </summary>
        /// <param name="languageCode">표시 이름을 가져올 언어 코드입니다.</param>
        /// <returns>언어 코드에 해당하는 표시 이름입니다. 해당 코드가 없으면 null을 반환합니다.</returns>
        public string GetLanguageDisplayName(string languageCode)
        {
            if (string.IsNullOrEmpty(languageCode) || _dataSource?.Languages == null)
            {
                return null;
            }
            _dataSource.Languages.TryGetValue(languageCode, out string displayName);
            return displayName;
        }

        /// <summary>
        /// <see cref="LocalizationManager"/> 클래스의 새 인스턴스를 초기화합니다.
        /// 기본 언어를 "ko-KR"로 설정합니다.
        /// 생성자를 private으로 변경하여 외부에서의 직접적인 인스턴스 생성을 막습니다.
        /// </summary>
        private LocalizationManager(string defaultLanguage = "ko-KR")
        {
            _dataSource = new LocalizationDataSource();
            // 기본 언어를 설정하거나, 시스템 설정을 따르도록 초기화할 수 있습니다.
            _currentLanguage = defaultLanguage; // 예시 기본 언어
        }

        /// <summary>
        /// 기본 경로(<see cref="Application.streamingAssetsPath"/>/locale.json)에서 다국어 데이터를 로드합니다.
        /// </summary>
        /// <returns>데이터 로드 성공 시 true, 실패 시 false를 반환합니다.</returns>
        public bool LoadDefaultLocalizationData()
        {
            string path = Path.Combine(Application.streamingAssetsPath, "locale.json");
            return LoadLocalizationData(path);
        }

        /// <summary>
        /// 지정된 파일 경로에서 JSON 형식의 다국어 데이터를 로드합니다.
        /// </summary>
        /// <param name="filePath">로드할 JSON 파일의 전체 경로입니다.</param>
        /// <returns>데이터 로드 및 분석 성공 시 true, 실패 시 false를 반환합니다.</returns>
        public bool LoadLocalizationData(string filePath)
        {
            if (!File.Exists(filePath))
            {
                ULog.Error($"Localization file not found: {filePath}", new System.Exception($"Localization file not found: {filePath}"));
                return false;
            }

            try
            {
                string jsonData = File.ReadAllText(filePath);
                // 직접 Dictionary로 역직렬화한 후 Translations에 할당
                var loadedDataSource = JsonHelper.FromJson<LocalizationDataSource>(jsonData);

                if (loadedDataSource == null || loadedDataSource.Words == null || loadedDataSource.Languages == null)
                {
                    ULog.Error($"Failed to deserialize localization data, or 'languages'/'words' section is missing in: {filePath}");
                    _dataSource = new LocalizationDataSource(); // 빈 데이터 소스로 초기화
                    return false;
                }


                _dataSource = loadedDataSource;

                // 데이터 로드 후, 현재 설정된 언어가 유효한지 확인하고,
                // 유효하지 않다면 사용 가능한 첫 번째 언어나 기본 언어로 설정할 수 있습니다.
                List<string> availableLangCodes = AvailableLanguages;
                if (!string.IsNullOrEmpty(_currentLanguage) && !availableLangCodes.Contains(_currentLanguage))
                {
                    ULog.Warning($"Current language '{_currentLanguage}' not found in the new data source's 'languages' section. Attempting to set to default or first available.");
                    if (availableLangCodes.Count > 0)
                    {
                        SetCurrentLanguage(availableLangCodes[0]);
                    }
                }
                return true;
            }
            catch (Exception ex)
            {
                ULog.Error($"Error loading localization data from {filePath}: {ex.Message}", ex);
                _dataSource = new LocalizationDataSource(); // 오류 발생 시 빈 데이터 소스로 초기화
                return false;
            }
        }

        /// <summary>
        /// 현재 사용 언어를 변경합니다.
        /// 성공적으로 언어가 변경되면 <see cref="OnLanguageChanged"/> 이벤트가 발생합니다.
        /// </summary>
        /// <param name="languageCode">설정할 언어 코드입니다 (예: "en-US", "ko-KR").</param>
        public void SetCurrentLanguage(string languageCode)
        {
            if (string.IsNullOrEmpty(languageCode))
            {
                ULog.Warning("Language code cannot be null or empty.");
                return;
            }

            if (_dataSource?.Languages == null || _dataSource.Languages.Count == 0)
            {
                ULog.Warning("Languages data source is not loaded or empty. Cannot set language.");
                return;
            }

            //AvailableLanguages를 통해 해당 언어가 지원되는지 확인합니다.
            if (_dataSource.Languages.ContainsKey(languageCode))
            {
                if (_currentLanguage != languageCode)
                {
                    _currentLanguage = languageCode;
                    OnLanguageChanged?.Invoke(_currentLanguage);
                    ULog.Debug($"Current language changed to: {languageCode} ({GetLanguageDisplayName(languageCode)})");
                }
            }
            else
            {
                ULog.Warning($"Language code '{languageCode}' not found in 'languages' data source. Current language remains '{_currentLanguage}'.");
            }
        }

        /// <summary>
        /// 현재 설정된 언어를 기준으로, 주어진 키에 해당하는 번역된 문자열을 가져옵니다.
        /// </summary>
        /// <param name="key">번역 문자열을 찾기 위한 키입니다.</param>
        /// <returns>
        /// 번역된 문자열을 반환합니다.
        /// 키가 null이거나 비어있으면 빈 문자열을 반환합니다.
        /// 해당 키 또는 현재 언어에 대한 번역을 찾을 수 없으면, "[키]" 형식의 문자열을 반환합니다.
        /// </returns>
        public string GetString(string key)
        {
            if (string.IsNullOrEmpty(key))
            {
                ULog.Warning("Requested localization key is null or empty.");
                return string.Empty;
            }

            if (_dataSource?.Words == null)
            {
                ULog.Warning("Words data source is not loaded. Cannot get string for key: " + key);
                return key;
            }

            if (_dataSource.Words.TryGetValue(key, out var languageSpecificStrings) &&
                languageSpecificStrings.TryGetValue(_currentLanguage, out var translatedString))
            {
                return translatedString;
            }

            //ULog.Debug($"Translation for key '{key}' not found in language '{_currentLanguage}'.");
            return key; // 키를 그대로 반환
        }

        /// <summary>
        /// 특정 언어의 특정 키에 대한 번역된 문자열을 직접 가져옵니다.
        /// </summary>
        /// <param name="languageCode">문자열을 가져올 대상 언어 코드입니다.</param>
        /// <param name="key">번역 문자열을 찾기 위한 키입니다.</param>
        /// <returns>
        /// 번역된 문자열을 반환합니다.
        /// 언어 코드나 키가 null이거나 비어있으면 빈 문자열을 반환합니다.
        /// 해당 언어 또는 키에 대한 번역을 찾을 수 없으면 null을 반환합니다.
        /// </returns>
        public string? GetString(string languageCode, string key)
        {
            if(string.IsNullOrEmpty(languageCode))
            {
                ULog.Warning("Requested language code is null or empty for GetString(languageCode, key).");
                return null;
            }
            if (string.IsNullOrEmpty(key))
            {
                ULog.Warning("Requested localization key is null or empty for GetString(languageCode, key).");
                return null;
            }

            if (_dataSource?.Words == null)
            {
                ULog.Warning("Words data source is not loaded. Cannot get string for key: " + key + " in language: " + languageCode);
                return null;
            }

            if (_dataSource.Words.TryGetValue(key, out var languageSpecificStrings) &&
                languageSpecificStrings.TryGetValue(languageCode, out var translatedString))
            {
                return translatedString;
            }

            ULog.Debug($"Translation for key '{key}' not found in language '{languageCode}' within 'words' data.");
            return null; // 해당 언어/키가 없음을 표시 (이전에는 "[languageCode:key]" 반환)
        }
    }
}
