#nullable enable

using Cysharp.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using UnityEngine;
using UVC.Log;
using UVC.Network;
using UVC.Tests;

namespace UVC.Data
{
    /// <summary>
    /// HTTP 요청 파이프라인을 관리하는 클래스입니다.
    /// </summary>
    /// <remarks>
    /// 이 클래스는 HTTP 요청의 실행 및 반복 요청을 관리합니다.
    /// 등록된 요청(HttpPipeLineInfo)을 키 값으로 관리하며, 
    /// 주기적 데이터 수집을 위한 반복 요청 기능을 제공합니다.
    /// 
    /// 주요 기능:
    /// - 단일 및 반복 HTTP 요청 관리
    /// - 요청 결과의 JSON 데이터를 IDataObject로 변환
    /// - 안전한 요청 취소 및 자원 정리
    /// - 테스트를 위한 목업 기능 지원
    /// 
    /// 모든 반복 실행은 CancellationTokenSource를 통해 취소할 수 있으며,
    /// 취소 후 현재 진행 중인 모든 요청이 안전하게 완료되는 것을 보장합니다.
    /// </remarks>
    /// <example>
    /// <code>
    /// // HttpPipeLine 인스턴스 생성
    /// var httpPipeline = new HttpPipeLine();
    /// 
    /// // 데이터 매퍼 설정 (응답 데이터 변환용)
    /// var dataMask = new DataMask();
    /// dataMask["name"] = "이름";
    /// dataMask["value"] = 0;
    /// var dataMapper = new DataMapper(dataMask);
    /// 
    /// // 단일 요청 설정 및 등록
    /// var singleRequest = new HttpPipeLineInfo("https://api.example.com/data")
    ///     .setDataMapper(dataMapper)
    ///     .setHandler(data => {
    ///         // 데이터 처리 로직
    ///         ULog.Debug($"데이터 수신: {data?.ToString() ?? "null"}");
    ///     });
    /// httpPipeline.Add("dataRequest", singleRequest);
    /// 
    /// // 반복 요청 설정 및 등록
    /// var repeatingRequest = new HttpPipeLineInfo("https://api.example.com/status")
    ///     .setDataMapper(dataMapper)
    ///     .setHandler(data => {
    ///         // 상태 데이터 처리
    ///         ULog.Debug($"상태 업데이트: {data?.ToString() ?? "null"}");
    ///     })
    ///     .setRepeat(true, 0, 5000); // 5초마다 무한 반복
    /// httpPipeline.Add("statusMonitor", repeatingRequest);
    /// 
    /// // 요청 실행
    /// await httpPipeline.Excute("dataRequest");   // 단일 실행
    /// await httpPipeline.Excute("statusMonitor"); // 반복 실행 시작
    /// 
    /// // 나중에 반복 요청 중지
    /// httpPipeline.StopRepeat("statusMonitor");
    /// 
    /// // 더 이상 필요없는 요청 제거
    /// httpPipeline.Remove("dataRequest");
    /// </code>
    /// </example>
    public class HttpPipeLine
    {
        /// <summary>
        /// 테스트를 위한 목업 모드 활성화 여부를 설정하거나 가져옵니다.
        /// </summary>
        /// <remarks>
        /// true로 설정하면 실제 HTTP 요청 대신 MockHttpRequester를 사용합니다.
        /// 테스트 환경에서 외부 의존성 없이 HTTP 요청을 시뮬레이션할 때 유용합니다.
        /// </remarks>
        public bool UseMockup { get; internal set; } = false;

        /// <summary>
        /// 등록된 HTTP 파이프라인 정보를 저장하는 사전
        /// </summary>
        private Dictionary<string, HttpPipeLineInfo> infoList = new Dictionary<string, HttpPipeLineInfo>();
        /// <summary>
        /// 실행 중인 반복 작업의 취소 토큰을 관리하는 사전
        /// </summary>
        private Dictionary<string, CancellationTokenSource> repeatTokenSources = new Dictionary<string, CancellationTokenSource>();

        /// <summary>
        /// 진행 중인 요청의 상태를 추적하는 사전입니다.
        /// </summary>
        /// <remarks>
        /// 키는 요청 식별자이고, 값은 현재 요청이 실행 중인지 여부를 나타냅니다.
        /// 이 상태 추적은 StopRepeat 메서드가 요청의 완전한 종료를 보장하기 위해 사용됩니다.
        /// </remarks>
        private Dictionary<string, bool> requestInProgress = new Dictionary<string, bool>();

        /// <summary>
        /// 새로운 HTTP 요청 정보를 추가하거나 기존 정보를 업데이트합니다.
        /// </summary>
        /// <param name="key">요청을 식별하는 키</param>
        /// <param name="info">HTTP 요청 정보</param>
        /// <remarks>
        /// 동일한 키가 이미 존재하는 경우 새로운 정보로 대체됩니다.
        /// </remarks>
        public void Add(string key, HttpPipeLineInfo info)
        {
            if (!infoList.ContainsKey(key))
            {
                infoList.Add(key, info);
            }
            else
            {
                infoList[key] = info; // Update existing entry
            }
        }

        /// <summary>
        /// 지정한 키의 HTTP 요청 정보를 제거합니다.
        /// </summary>
        /// <param name="key">제거할 요청의 키</param>
        /// <remarks>
        /// 실행 중인 반복 작업이 있다면 함께 중지됩니다.
        /// </remarks>
        public async UniTask RemoveAsync(string key)
        {
            if (infoList.ContainsKey(key))
            {
                await StopRepeat(key);
                infoList.Remove(key);
            }
        }

        /// <summary>
        /// 지정한 키의 HTTP 요청을 실행합니다.
        /// </summary>
        /// <param name="key">실행할 요청의 키</param>
        /// <returns>비동기 작업</returns>
        /// <remarks>
        /// 요청 정보의 repeat 속성에 따라 단일 실행 또는 반복 실행을 시작합니다.
        /// 이미 실행 중인 반복 작업이 있다면 먼저 중지하고 완료를 대기한 후 새로운 요청을 시작합니다.
        /// 단일 실행의 경우 완료될 때까지 대기하지만, 반복 실행은 백그라운드에서 실행됩니다.
        /// </remarks>
        /// <exception cref="KeyNotFoundException">지정된 키가 등록되어 있지 않은 경우</exception>
        public async UniTask Excute(string key)
        {
            if (infoList.ContainsKey(key))
            {
                HttpPipeLineInfo info = infoList[key];

                // 반복 설정에 관계없이 이전에 실행 중인 반복 작업이 있다면 중지
                await StopRepeat(key);

                if (!info.Repeat)
                {
                    // 단일 실행 로직 호출
                    await ExecuteSingle(key, info);
                }
                else
                {
                    // 반복 설정이 있는 경우에만 StartRepeat 호출
                    StartRepeat(key).Forget();
                }
            }
            else
            {
                throw new KeyNotFoundException($"No HTTP request found with key '{key}'.");
            }
        }

        /// <summary>
        /// 단일 HTTP 요청을 실행하고 결과를 처리합니다.
        /// </summary>
        /// <param name="key">요청을 식별하는 키</param>
        /// <param name="info">HTTP 요청 정보</param>
        /// <param name="cancellationToken">요청 취소를 위한 취소 토큰</param>
        /// <returns>비동기 작업</returns>
        /// <remarks>
        /// 이 메서드는 HTTP 요청을 보내고, 응답 데이터를 파싱하여 IDataObject로 변환합니다.
        /// JSON 객체 또는 배열 형식의 응답을 처리할 수 있으며, 취소 토큰을 통해 언제든지 작업을 취소할 수 있습니다.
        /// </remarks>
        /// <exception cref="OperationCanceledException">작업이 취소된 경우 발생</exception>
        /// <exception cref="JsonException">JSON 응답 파싱 중 오류가 발생한 경우</exception>
        /// <exception cref="Exception">HTTP 요청 중 다른 예외가 발생한 경우</exception>
        private async UniTask ExecuteSingle(string key, HttpPipeLineInfo info, CancellationToken cancellationToken = default)
        {

            int retryCount = 0;
            Exception lastException = null;

            while (retryCount <= info.MaxRetryCount)
            {
                // 취소 요청 확인
                if (cancellationToken.IsCancellationRequested)
                {
                    throw new OperationCanceledException("Operation cancelled", cancellationToken);
                }

                lock (requestInProgress)
                {
                    requestInProgress[key] = true;
                }

                try
                {
                    string result = string.Empty;
                    if (!UseMockup)
                    {
                        result = await HttpRequester.Request<string>(info.Url, info.Method, info.Body, info.Headers);
                    }
                    else
                    {
                        result = await MockHttpRequester.Request<string>(info.Url, info.Method, info.Body, info.Headers);
                    }

                    // 응답 처리 전에 다시 취소 요청 확인
                    if (cancellationToken.IsCancellationRequested)
                    {
                        throw new OperationCanceledException("Operation cancelled", cancellationToken);
                    }

                    IDataObject? dataObject = null;
                    result = result.Trim();

                    if (!string.IsNullOrEmpty(result))
                    {
                        try
                        {
                            HttpResponseResult responseResult = info.ResponseMask.Apply(result);
                            // 응답 마스크 적용 결과가 성공이 아니면 실패 핸들러 호출 후 반환
                            if (!responseResult.IsSuccess)
                            {
                                info.FailHandler?.Invoke(responseResult.Message!);
                                return;
                            }
                            else
                            {
                                result = responseResult.Data!.Trim();
                            }


                            if (result.StartsWith("{"))
                            {
                                if (info.DataMapper != null)
                                {
                                    if (info.DataMapper.SupportsStreamParsing && result.Length > info.DataMapper.SupportsStreamLength)
                                    {
                                        using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(result)))
                                        {
                                            dataObject = info.DataMapper.MapObjectStream(stream);
                                        }
                                    }
                                    else
                                    {
                                        JObject source = JObject.Parse(result);
                                        dataObject = info.DataMapper.Map(source);
                                    }
                                }
                            }
                            else if (result.StartsWith("["))
                            {
                                if (info.DataMapper != null)
                                {
                                    if (info.DataMapper.SupportsStreamParsing && result.Length > info.DataMapper.SupportsStreamLength)
                                    {
                                        using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(result)))
                                        {
                                            dataObject = info.DataMapper.MapArrayStream(stream);
                                        }
                                    }
                                    else
                                    {
                                        JArray source = JArray.Parse(result);
                                        dataObject = info.DataMapper.Map(source);
                                    }
                                }
                            }
                        }
                        catch (JsonException ex)
                        {
                            ULog.Error($"JSON parsing error in ExecuteSingle for {key}: {ex.Message}\nResponse: {result}", ex);
                            throw; // 상위에서 처리하도록 다시 throw
                        }
                    }

                    // 핸들러 호출 전에 다시 취소 요청 확인
                    if (cancellationToken.IsCancellationRequested)
                    {
                        throw new OperationCanceledException("Operation cancelled", cancellationToken);
                    }
                    if (dataObject != null) dataObject = DataRepository.Instance.AddData(key, dataObject, info.UpdatedDataOnly);

                    // 갱신 된 데이터가 있는 경우 핸들러 호출
                    if (info.UpdatedDataOnly)
                    {
                        if (dataObject != null && dataObject.UpdatedCount > 0) info.SuccessHandler?.Invoke(dataObject);
                    }
                    else
                    {
                        info.SuccessHandler?.Invoke(dataObject);
                    }
                    return;
                }
                catch (OperationCanceledException)
                {
                    // 취소 예외는 그대로 전파
                    throw;
                }
                catch (Exception ex)
                {
                    lastException = ex;
                    retryCount++;

                    if (retryCount <= info.MaxRetryCount)
                    {
                        // 재시도 전에 취소 요청 확인
                        if (cancellationToken.IsCancellationRequested)
                        {
                            throw new OperationCanceledException("Operation cancelled", cancellationToken);
                        }
                        ULog.Warning($"Request failed for '{key}', retry {retryCount}/{info.MaxRetryCount} after {info.RetryDelay}ms: {ex.Message}", ex);
                        await UniTask.Delay(info.RetryDelay);
                    }
                }
                finally
                {
                    lock (requestInProgress)
                    {
                        requestInProgress[key] = false;
                    }
                }
            }

            // 모든 재시도 후에도 실패
            ULog.Error($"Request failed for '{key}' after {info.MaxRetryCount} retries: {lastException?.Message}", lastException);
            throw lastException;
        }

        /// <summary>
        /// 반복 실행을 시작합니다.
        /// </summary>
        /// <param name="key">반복 실행할 요청의 키</param>
        /// <returns>비동기 작업</returns>
        /// <remarks>
        /// 지정된 간격(repeatInterval)으로 HTTP 요청을 반복 실행합니다.
        /// repeatCount가 0인 경우 무한 반복하며, 0보다 큰 경우 지정된 횟수만큼만 실행합니다.
        /// 작업 실행 중 예외가 발생하면 로그를 기록하고 다음 실행을 시도합니다.
        /// 취소 요청이 있거나 최대 실행 횟수에 도달하면 반복이 종료됩니다.
        /// </remarks>
        /// <exception cref="KeyNotFoundException">지정된 키가 등록되어 있지 않은 경우</exception>
        private async UniTask StartRepeat(string key)
        {
            if (!infoList.ContainsKey(key))
            {
                throw new KeyNotFoundException($"No HTTP request found with key '{key}'.");
                return;
            }

            HttpPipeLineInfo info = infoList[key];
            if (!info.Repeat) return;

            // 새 취소 토큰 생성
            CancellationTokenSource cts = new CancellationTokenSource();
            repeatTokenSources[key] = cts;

            int executionCount = 0;

            try
            {
                while (!cts.IsCancellationRequested)
                {
                    try
                    {
                        // 단일 실행 로직 호출
                        await ExecuteSingle(key, info, cts.Token);

                        // 지정된 횟수만큼 반복한 경우 중지
                        if (info.RepeatCount > 0)
                        {
                            executionCount++;
                            if (executionCount >= info.RepeatCount)
                            {
                                break;
                            }
                        }
                        // 토큰이 취소되지 않은 경우에만 지연
                        if (!cts.IsCancellationRequested)
                        {
                            // 지정된 간격만큼 대기
                            await UniTask.Delay(info.RepeatInterval, cancellationToken: cts.Token);
                        }
                    }
                    catch (OperationCanceledException)
                    {
                        // 취소된 경우 루프 종료
                        break;
                    }
                    catch (Exception ex)
                    {
                        // 다른 예외 처리
                        ULog.Error($"Error in repeat execution for '{key}': {ex.Message}", ex);
                        if (cts.IsCancellationRequested)
                        {
                            break;
                        }

                        try
                        {
                            await UniTask.Delay(info.RepeatInterval, cancellationToken: cts.Token);
                        }
                        catch (OperationCanceledException)
                        {
                            // 취소된 경우 무시하고 루프 종료
                            break;
                        }
                    }
                }
            }
            finally
            {
                lock (repeatTokenSources) // 스레드 안전성 확보
                {
                    if (repeatTokenSources.TryGetValue(key, out var currentCts) && currentCts == cts)
                    {
                        repeatTokenSources.Remove(key);
                    }
                }
                cts.Dispose(); // 여기서 최종적으로 Dispose 호출
            }
        }

        /// <summary>
        /// 반복 실행 중인 요청을 중지합니다.
        /// </summary>
        /// <param name="key">중지할 요청의 키</param>
        /// <returns>요청 중지 작업을 나타내는 비동기 작업</returns>
        /// <remarks>
        /// 해당 키로 실행 중인 반복 작업이 없는 경우 아무 작업도 수행하지 않습니다.
        /// 요청이 중지되었더라도 현재 실행 중인 작업이 완전히 종료될 때까지 대기합니다.
        /// 이를 통해 작업 중단 후 자원이 안전하게 정리되는 것을 보장합니다.
        /// </remarks>
        public async UniTask StopRepeat(string key)
        {
            CancellationTokenSource cts = null;
            lock (repeatTokenSources) // 스레드 안전성 확보
            {
                if (repeatTokenSources.TryGetValue(key, out cts) && !cts.IsCancellationRequested)
                {
                    cts.Cancel();
                    repeatTokenSources.Remove(key);
                }
            }

            // 진행 중인 요청이 완료될 때까지 대기
            if (cts != null)
            {
                while (true)
                {
                    lock (requestInProgress)
                    {
                        if (!requestInProgress.ContainsKey(key) || !requestInProgress[key])
                        {
                            break;
                        }
                    }
                    await UniTask.Delay(10);
                }
            }
        }

        /// <summary>
        /// 모든 반복 실행 중인 요청을 중지합니다.
        /// </summary>
        /// <remarks>
        /// 애플리케이션 종료 시 또는 모든 반복 작업을 일괄 중지해야 할 때 사용합니다.
        /// 이 메서드는 비동기적으로 작동하지만 완료를 대기하지 않습니다.
        /// 모든 작업이 완전히 종료될 때까지 기다려야 하는 경우, 각 키에 대해 개별적으로 StopRepeat를 호출하고 대기해야 합니다.
        /// </remarks>
        public async UniTask StopAllRepeats()
        {
            foreach (var key in new List<string>(repeatTokenSources.Keys))
            {
                await StopRepeat(key);
            }
        }


        /// <summary>
        /// 현재 활성화된 요청 목록과 상태 정보를 반환합니다.
        /// </summary>
        /// <returns>키와 요청 상태 정보를 포함하는 딕셔너리</returns>
        /// <remarks>
        /// 반환되는 딕셔너리는 등록된 모든 HTTP 요청에 대한 상태 정보를 제공합니다.
        /// 각 요청에 대해 활성 상태, 반복 설정, 반복 횟수, 반복 간격을 확인할 수 있습니다.
        /// </remarks>
        /// <example>
        /// <code>
        /// var httpPipeline = new HttpPipeLine();
        /// // 파이프라인에 요청 추가 후...
        /// 
        /// // 모든 활성 요청 확인
        /// var activeRequests = httpPipeline.GetActiveRequests();
        /// foreach (var request in activeRequests)
        /// {
        ///     ULog.Debug($"요청 키: {request.Key}, 활성 상태: {request.Value.IsActive}, " +
        ///               $"반복 중: {request.Value.IsRepeating}, 반복 간격: {request.Value.RepeatInterval}ms");
        /// }
        /// </code>
        /// </example>
        public Dictionary<string, HttpPipeLineRequestStatus> GetActiveRequests()
        {
            var result = new Dictionary<string, HttpPipeLineRequestStatus>();
            foreach (var key in infoList.Keys)
            {
                bool isRepeating = repeatTokenSources.ContainsKey(key);
                result[key] = new HttpPipeLineRequestStatus
                {
                    IsActive = isRepeating,
                    IsRepeating = isRepeating,
                    RepeatCount = isRepeating ? infoList[key].RepeatCount : 0,
                    RepeatInterval = isRepeating ? infoList[key].RepeatInterval : 0
                };
            }
            return result;
        }

        /// <summary>
        /// 현재 인스턴스에서 사용되는 모든 리소스를 해제하고 진행 중인 모든 작업을 중지합니다.
        /// </summary>
        /// <remarks>이 메서드는 모든 반복 작업을 중단하고, 내부 상태를 지우고, 관련 리소스를 삭제합니다. 
        /// <see cref="Dispose"/>를 호출한 후에는 해당 인스턴스를 더 이상 사용할 수 없습니다.</remarks>
        public void Dispose()
        {
            // 모든 반복 작업 중지
            StopAllRepeats().Forget();
            // 요청 상태 초기화
            requestInProgress.Clear();
            // 등록된 요청 정보 초기화
            infoList.Clear();
            // 취소 토큰 소스 정리
            foreach (var cts in repeatTokenSources.Values)
            {
                cts.Dispose();
            }
            repeatTokenSources.Clear();
        }

    }

    /// <summary>
    /// HTTP 요청의 현재 상태 정보를 나타내는 클래스입니다.
    /// </summary>
    /// <remarks>
    /// 이 클래스는 HTTP 파이프라인에 등록된 요청의 활성 상태, 반복 설정, 
    /// 반복 횟수, 반복 간격에 관한 정보를 제공합니다.
    /// </remarks>
    public class HttpPipeLineRequestStatus
    {
        /// <summary>
        /// 요청이 현재 활성 상태인지 여부를 나타냅니다.
        /// </summary>
        public bool IsActive { get; set; }
        /// <summary>
        /// 요청이 반복 실행 중인지 여부를 나타냅니다.
        /// </summary>
        public bool IsRepeating { get; set; }
        /// <summary>
        /// 반복 설정된 횟수를 나타냅니다. 0은 무한 반복을 의미합니다.
        /// </summary>
        public int RepeatCount { get; set; }
        /// <summary>
        /// 반복 요청 간의 간격을 밀리초 단위로 나타냅니다.
        /// </summary>
        public int RepeatInterval { get; set; }
    }
}
