#nullable enable
using System;
using System.Globalization;
using System.Threading.Tasks;
using IGP.UnitySDK.Models;
using UnityEngine.Events;

namespace IGP.UnitySDK.Compliance
{
    public static class IGPAntiAddictionComplianceEventKeys
    {
        public const string Disabled = "DISABLED";
        public const string Allowed = "ALLOWED";
        public const string RealNameRequired = "REAL_NAME_REQUIRED";
        public const string RealNameVerifying = "REAL_NAME_VERIFYING";
        public const string RealNameFailed = "REAL_NAME_FAILED";
        public const string PeriodRestricted = "PERIOD_RESTRICTED";
        public const string DurationLimited = "DURATION_LIMITED";
        public const string AgeLimitBlocked = "AGE_LIMIT_BLOCKED";
        public const string ConcurrentGameLimit = "CONCURRENT_GAME_LIMIT";
        public const string ConfigIncomplete = "CONFIG_INCOMPLETE";
        public const string ServiceUnavailable = "SERVICE_UNAVAILABLE";
        public const string Blocked = "BLOCKED";
    }

    public static class IGPAntiAddictionComplianceActions
    {
        public const string Allow = "ALLOW";
        public const string Block = "BLOCK";
    }

    public static class IGPAntiAddictionComplianceEventCodes
    {
        public const int Disabled = 100;
        public const int Allowed = 200;
        public const int RealNameRequired = 300;
        public const int RealNameVerifying = 310;
        public const int RealNameFailed = 320;
        public const int PeriodRestricted = 400;
        public const int DurationLimited = 410;
        public const int AgeLimitBlocked = 420;
        public const int ConcurrentGameLimit = 430;
        public const int ConfigIncomplete = 800;
        public const int ServiceUnavailable = 900;
        public const int Blocked = 999;
    }

    [Serializable]
    public sealed class IGPAntiAddictionComplianceEvent
    {
        public string module = "anti-addiction";
        public int code;
        public string key = IGPAntiAddictionComplianceEventKeys.Disabled;
        public string action = IGPAntiAddictionComplianceActions.Allow;
        public bool canPlayNow = true;
        public string reasonCode = string.Empty;
        public string reasonMessage = string.Empty;
        public IGPAntiAddictionStatus status = new IGPAntiAddictionStatus();
    }

    [Serializable]
    public sealed class IGPAntiAddictionAgeRangeResult
    {
        public int ageRange = -1;
        public string ageBand = nameof(IGPAgeBand.Unknown);
        public IGPAntiAddictionStatus status = new IGPAntiAddictionStatus();
    }

    [Serializable]
    public sealed class IGPAntiAddictionRemainingTimeResult
    {
        public int remainingTimeSeconds = -1;
        public int remainingTimeMinutes = -1;
        public IGPAntiAddictionStatus status = new IGPAntiAddictionStatus();
    }

    public static class IGPCompliance
    {
        public const string DefaultServiceUnavailableMessage = "网络或服务异常";

        public static IGPAntiAddictionComplianceEvent CurrentAntiAddictionEvent(
            IGPRuntimeManager runtimeManager)
        {
            if (runtimeManager == null)
            {
                throw new ArgumentNullException(nameof(runtimeManager));
            }

            return ToAntiAddictionComplianceEvent(runtimeManager.CurrentAntiAddictionStatus);
        }

        public static async Task<IGPAntiAddictionComplianceEvent> RefreshAntiAddictionEventAsync(
            IGPRuntimeManager runtimeManager,
            int? appIdOverride = null)
        {
            if (runtimeManager == null)
            {
                throw new ArgumentNullException(nameof(runtimeManager));
            }

            var status = await runtimeManager.RefreshAntiAddictionStatusAsync(appIdOverride);
            return ToAntiAddictionComplianceEvent(status);
        }

        public static IGPAntiAddictionAgeRangeResult CurrentAntiAddictionAgeRange(
            IGPRuntimeManager runtimeManager)
        {
            if (runtimeManager == null)
            {
                throw new ArgumentNullException(nameof(runtimeManager));
            }

            return ToAntiAddictionAgeRangeResult(runtimeManager.CurrentAntiAddictionStatus);
        }

        public static async Task<IGPAntiAddictionAgeRangeResult> RefreshAntiAddictionAgeRangeAsync(
            IGPRuntimeManager runtimeManager,
            int? appIdOverride = null)
        {
            if (runtimeManager == null)
            {
                throw new ArgumentNullException(nameof(runtimeManager));
            }

            var status = await runtimeManager.RefreshAntiAddictionStatusAsync(appIdOverride);
            return ToAntiAddictionAgeRangeResult(status);
        }

        public static IGPAntiAddictionRemainingTimeResult CurrentAntiAddictionRemainingTime(
            IGPRuntimeManager runtimeManager)
        {
            if (runtimeManager == null)
            {
                throw new ArgumentNullException(nameof(runtimeManager));
            }

            return ToAntiAddictionRemainingTimeResult(runtimeManager.CurrentAntiAddictionStatus);
        }

        public static async Task<IGPAntiAddictionRemainingTimeResult> RefreshAntiAddictionRemainingTimeAsync(
            IGPRuntimeManager runtimeManager,
            int? appIdOverride = null)
        {
            if (runtimeManager == null)
            {
                throw new ArgumentNullException(nameof(runtimeManager));
            }

            var status = await runtimeManager.RefreshAntiAddictionStatusAsync(appIdOverride);
            return ToAntiAddictionRemainingTimeResult(status);
        }

        public static IDisposable SubscribeAntiAddictionEvents(
            IGPRuntimeManager runtimeManager,
            Action<IGPAntiAddictionComplianceEvent> handler)
        {
            if (runtimeManager == null)
            {
                throw new ArgumentNullException(nameof(runtimeManager));
            }

            if (handler == null)
            {
                throw new ArgumentNullException(nameof(handler));
            }

            UnityAction<IGPAntiAddictionStatus> listener =
                status => handler(ToAntiAddictionComplianceEvent(status));
            runtimeManager.onAntiAddictionStateChanged.AddListener(listener);
            return new ComplianceEventSubscription(
                () => runtimeManager.onAntiAddictionStateChanged.RemoveListener(listener));
        }

        public static IGPAntiAddictionStatus CreateServiceUnavailableStatus(
            string? message = null)
        {
            return new IGPAntiAddictionStatus
            {
                enabled = true,
                canPlayNow = false,
                state = nameof(IGPAntiAddictionState.Blocked),
                reasonCode = "ANTI_ADDICTION_SERVICE_UNAVAILABLE",
                reasonMessage = string.IsNullOrWhiteSpace(message)
                    ? DefaultServiceUnavailableMessage
                    : message!,
                ageBand = nameof(IGPAgeBand.Unknown),
                evaluatedAt = DateTimeOffset.UtcNow.ToString("O"),
            };
        }

        public static IGPAntiAddictionComplianceEvent ToAntiAddictionComplianceEvent(
            IGPAntiAddictionStatus? status)
        {
            var effectiveStatus = status ?? CreateServiceUnavailableStatus();
            var key = ResolveEventKey(effectiveStatus);
            var action = IsAllowedKey(key)
                ? IGPAntiAddictionComplianceActions.Allow
                : IGPAntiAddictionComplianceActions.Block;

            return new IGPAntiAddictionComplianceEvent
            {
                module = "anti-addiction",
                code = ResolveEventCode(key),
                key = key,
                action = action,
                canPlayNow = string.Equals(
                    action,
                    IGPAntiAddictionComplianceActions.Allow,
                    StringComparison.Ordinal),
                reasonCode = effectiveStatus.reasonCode ?? string.Empty,
                reasonMessage = effectiveStatus.reasonMessage ?? string.Empty,
                status = effectiveStatus,
            };
        }

        public static IGPAntiAddictionAgeRangeResult ToAntiAddictionAgeRangeResult(
            IGPAntiAddictionStatus? status)
        {
            var effectiveStatus = status ?? CreateServiceUnavailableStatus();
            return new IGPAntiAddictionAgeRangeResult
            {
                ageRange = ToAntiAddictionAgeRange(effectiveStatus.ageBand),
                ageBand = string.IsNullOrWhiteSpace(effectiveStatus.ageBand)
                    ? nameof(IGPAgeBand.Unknown)
                    : effectiveStatus.ageBand,
                status = effectiveStatus,
            };
        }

        public static IGPAntiAddictionRemainingTimeResult ToAntiAddictionRemainingTimeResult(
            IGPAntiAddictionStatus? status,
            DateTimeOffset? now = null)
        {
            var effectiveStatus = status ?? CreateServiceUnavailableStatus();
            var remainingTimeSeconds = ToAntiAddictionRemainingTimeSeconds(
                effectiveStatus,
                now ?? DateTimeOffset.UtcNow);

            return new IGPAntiAddictionRemainingTimeResult
            {
                remainingTimeSeconds = remainingTimeSeconds,
                remainingTimeMinutes = remainingTimeSeconds < 0
                    ? -1
                    : (int)Math.Ceiling(remainingTimeSeconds / 60d),
                status = effectiveStatus,
            };
        }

        public static int ToAntiAddictionAgeRange(string? ageBand)
        {
            switch (ParseAgeBand(ageBand))
            {
                case IGPAgeBand.Under8:
                    return 0;
                case IGPAgeBand.Age8To15:
                    return 8;
                case IGPAgeBand.Age16To17:
                    return 16;
                case IGPAgeBand.Adult:
                    return 18;
                default:
                    return -1;
            }
        }

        public static int ToAntiAddictionRemainingTimeSeconds(
            IGPAntiAddictionStatus? status,
            DateTimeOffset? now = null)
        {
            if (status == null)
            {
                return 0;
            }

            if (!status.enabled)
            {
                return -1;
            }

            if (!status.canPlayNow)
            {
                return 0;
            }

            if (string.IsNullOrWhiteSpace(status.playableUntil))
            {
                return -1;
            }

            if (!DateTimeOffset.TryParse(
                    status.playableUntil,
                    CultureInfo.InvariantCulture,
                    DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
                    out var playableUntil))
            {
                return -1;
            }

            var seconds = (playableUntil - (now ?? DateTimeOffset.UtcNow)).TotalSeconds;
            return seconds <= 0 ? 0 : (int)Math.Ceiling(seconds);
        }

        private static string ResolveEventKey(IGPAntiAddictionStatus status)
        {
            if (status == null)
            {
                return IGPAntiAddictionComplianceEventKeys.ServiceUnavailable;
            }

            if (!status.enabled)
            {
                return IGPAntiAddictionComplianceEventKeys.Disabled;
            }

            switch (status.reasonCode)
            {
                case "ANTI_ADDICTION_SERVICE_UNAVAILABLE":
                    return IGPAntiAddictionComplianceEventKeys.ServiceUnavailable;
                case "MINOR_PERIOD_RESTRICTED":
                    return IGPAntiAddictionComplianceEventKeys.PeriodRestricted;
                case "MINOR_DURATION_LIMIT":
                    return IGPAntiAddictionComplianceEventKeys.DurationLimited;
                case "AGE_LIMIT_BLOCKED":
                    return IGPAntiAddictionComplianceEventKeys.AgeLimitBlocked;
                case "MINOR_CONCURRENT_GAME_LIMIT":
                    return IGPAntiAddictionComplianceEventKeys.ConcurrentGameLimit;
                case "CONFIG_INCOMPLETE":
                    return IGPAntiAddictionComplianceEventKeys.ConfigIncomplete;
            }

            switch (ParseState(status.state))
            {
                case IGPAntiAddictionState.Allowed:
                    return status.canPlayNow
                        ? IGPAntiAddictionComplianceEventKeys.Allowed
                        : IGPAntiAddictionComplianceEventKeys.Blocked;
                case IGPAntiAddictionState.PendingRealName:
                    return IGPAntiAddictionComplianceEventKeys.RealNameRequired;
                case IGPAntiAddictionState.Verifying:
                    return IGPAntiAddictionComplianceEventKeys.RealNameVerifying;
                case IGPAntiAddictionState.Failed:
                    return IGPAntiAddictionComplianceEventKeys.RealNameFailed;
                case IGPAntiAddictionState.Blocked:
                    return IGPAntiAddictionComplianceEventKeys.Blocked;
                case IGPAntiAddictionState.Disabled:
                    return IGPAntiAddictionComplianceEventKeys.Disabled;
                default:
                    return status.canPlayNow
                        ? IGPAntiAddictionComplianceEventKeys.Allowed
                        : IGPAntiAddictionComplianceEventKeys.Blocked;
            }
        }

        private static bool IsAllowedKey(string key)
        {
            return string.Equals(key, IGPAntiAddictionComplianceEventKeys.Disabled, StringComparison.Ordinal) ||
                   string.Equals(key, IGPAntiAddictionComplianceEventKeys.Allowed, StringComparison.Ordinal);
        }

        private static IGPAntiAddictionState ParseState(string? state)
        {
            return Enum.TryParse(state, true, out IGPAntiAddictionState parsed)
                ? parsed
                : IGPAntiAddictionState.Disabled;
        }

        private static IGPAgeBand ParseAgeBand(string? ageBand)
        {
            return Enum.TryParse(ageBand, true, out IGPAgeBand parsed)
                ? parsed
                : IGPAgeBand.Unknown;
        }

        private static int ResolveEventCode(string key)
        {
            switch (key)
            {
                case IGPAntiAddictionComplianceEventKeys.Disabled:
                    return IGPAntiAddictionComplianceEventCodes.Disabled;
                case IGPAntiAddictionComplianceEventKeys.Allowed:
                    return IGPAntiAddictionComplianceEventCodes.Allowed;
                case IGPAntiAddictionComplianceEventKeys.RealNameRequired:
                    return IGPAntiAddictionComplianceEventCodes.RealNameRequired;
                case IGPAntiAddictionComplianceEventKeys.RealNameVerifying:
                    return IGPAntiAddictionComplianceEventCodes.RealNameVerifying;
                case IGPAntiAddictionComplianceEventKeys.RealNameFailed:
                    return IGPAntiAddictionComplianceEventCodes.RealNameFailed;
                case IGPAntiAddictionComplianceEventKeys.PeriodRestricted:
                    return IGPAntiAddictionComplianceEventCodes.PeriodRestricted;
                case IGPAntiAddictionComplianceEventKeys.DurationLimited:
                    return IGPAntiAddictionComplianceEventCodes.DurationLimited;
                case IGPAntiAddictionComplianceEventKeys.AgeLimitBlocked:
                    return IGPAntiAddictionComplianceEventCodes.AgeLimitBlocked;
                case IGPAntiAddictionComplianceEventKeys.ConcurrentGameLimit:
                    return IGPAntiAddictionComplianceEventCodes.ConcurrentGameLimit;
                case IGPAntiAddictionComplianceEventKeys.ConfigIncomplete:
                    return IGPAntiAddictionComplianceEventCodes.ConfigIncomplete;
                case IGPAntiAddictionComplianceEventKeys.ServiceUnavailable:
                    return IGPAntiAddictionComplianceEventCodes.ServiceUnavailable;
                default:
                    return IGPAntiAddictionComplianceEventCodes.Blocked;
            }
        }

        private sealed class ComplianceEventSubscription : IDisposable
        {
            private Action? disposeAction;

            public ComplianceEventSubscription(Action disposeAction)
            {
                this.disposeAction = disposeAction;
            }

            public void Dispose()
            {
                var action = disposeAction;
                disposeAction = null;
                action?.Invoke();
            }
        }
    }
}
