#nullable enable
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using IGP.UnitySDK;
using IGP.UnitySDK.Models;

namespace IGP.UnitySDK.Core
{
    /// <summary>
    /// Hosted WebSocket façade. Public SDK message and state APIs remain available,
    /// but all transport is delegated to the Curio desktop host session bridge.
    /// </summary>
    internal sealed class IGPHostedRealtimeClient : IDisposable
    {
        private const string LogScope = "hosted-realtime";
        private IGPHostSessionClient? hostSession;

        public string? RoomId { get; private set; }
        public string? PlayerId { get; private set; }
        public bool IsConnected { get; private set; }
        public IGPLogLevel LogLevel { get; set; }
        public bool DebugLogging
        {
            get => LogLevel != IGPLogLevel.Off;
            set => LogLevel = value ? IGPLogLevel.Debug : IGPLogLevel.Off;
        }

        public event Action<Message>? MessageReceived;
        public event Action<bool>? ConnectionStateChanged;
        public event Action<string>? ErrorOccurred;

        public IGPHostedRealtimeClient(string _unusedWsUrl = "")
        {
        }

        public Task ConnectAsync(string roomId, string playerId, string token, CancellationToken cancellationToken = default)
        {
            throw new IGPSDKException("Direct room connection is not supported; use Curio desktop hosted bootstrap");
        }

        public Task DisconnectAsync()
        {
            ClearHostedSession();
            return Task.CompletedTask;
        }

        internal void AttachHostedSession(IGPHostSessionClient session, string roomId, string playerId)
        {
            if (session == null)
            {
                throw new ArgumentNullException(nameof(session));
            }

            ClearHostedSession();

            hostSession = session;
            RoomId = roomId;
            PlayerId = playerId;
            LogInfo($"event=attach room={roomId} player={playerId}");

            hostSession.MessageReceived += HandleMessage;
            hostSession.ErrorOccurred += HandleHostSessionError;
            hostSession.Detached += HandleHostSessionDetached;
        }

        internal void UpdateHostedContext(string roomId, string playerId)
        {
            RoomId = roomId;
            PlayerId = playerId;
        }

        internal void UpdateHostedConnectionState(bool connected)
        {
            if (IsConnected == connected)
            {
                return;
            }

            IsConnected = connected;
            LogInfo($"event=state connected={connected} room={RoomId ?? string.Empty} player={PlayerId ?? string.Empty}");
            ConnectionStateChanged?.Invoke(connected);
        }

        internal void ClearHostedSession()
        {
            if (hostSession != null)
            {
                hostSession.MessageReceived -= HandleMessage;
                hostSession.ErrorOccurred -= HandleHostSessionError;
                hostSession.Detached -= HandleHostSessionDetached;
                hostSession = null;
            }

            RoomId = null;
            PlayerId = null;
            UpdateHostedConnectionState(false);
        }

        public async Task SendMessageAsync(Message message)
        {
            var session = EnsureHostedSession();
            LogDebug($"event=message-send type={message.type} target={message.targetPlayerId} reliable={message.reliable}");
            await session.SendMessageAsync(message);
        }

        public async Task SendPingAsync()
        {
            var session = EnsureHostedSession();
            LogDebug("event=ping-send");
            await session.SendPingAsync();
        }

        public async Task SetStateAsync(string scope, string key, object? value, string? targetPlayerId = null)
        {
            var session = EnsureHostedSession();
            LogDebug($"event=state-set scope={scope} key={key} target={targetPlayerId ?? string.Empty}");
            await session.SetStateAsync(scope, key, value, targetPlayerId);
        }

        public async Task GetStateAsync(string scope, string key, string? targetPlayerId = null)
        {
            var session = EnsureHostedSession();
            LogDebug($"event=state-get scope={scope} key={key} target={targetPlayerId ?? string.Empty}");
            await session.GetStateAsync(scope, key, targetPlayerId);
        }

        public async Task ResetStateAsync(string scope, string[]? keysToExclude = null)
        {
            var session = EnsureHostedSession();
            LogDebug($"event=state-reset scope={scope} excludedKeys={keysToExclude?.Length ?? 0}");
            await session.ResetStateAsync(scope, keysToExclude);
        }

        public void Dispose()
        {
            ClearHostedSession();
        }

        private IGPHostSessionClient EnsureHostedSession()
        {
            if (hostSession == null || !hostSession.IsAttached)
            {
                throw new IGPSDKException("IGP hosted session is not attached");
            }

            if (!IsConnected)
            {
                throw new IGPSDKException("IGP hosted realtime connection is not ready");
            }

            return hostSession;
        }

        private void HandleMessage(Message message)
        {
            LogDebug($"event=message-recv type={message.type} player={message.playerId} target={message.targetPlayerId}");
            MessageReceived?.Invoke(message);
        }

        private void HandleHostSessionDetached(string reason)
        {
            UpdateHostedConnectionState(false);
            ErrorOccurred?.Invoke(reason);
        }

        private void HandleHostSessionError(string code, string message)
        {
            ErrorOccurred?.Invoke(message);
        }

        private void LogDebug(string message)
        {
            IGPLog.Debug(LogScope, "client", message);
        }

        private void LogInfo(string message)
        {
            IGPLog.Info(LogScope, "client", message);
        }
    }

    /// <summary>
    /// 房间列表包装类
    /// </summary>
    [Serializable]
    public class IGPRoomList
    {
        public List<Room> rooms = new List<Room>();
    }
}
