using System;
using System.Threading.Tasks;
using UnityEngine;
using CoreClient = RudderSdk.Core.RudderClient;

namespace RudderSdk.Unity
{
  public enum RudderState
  {
    NotInitialized,
    Initializing,
    Ready,
    Failed
  }

  /// <summary>
  /// Scene bootstrap MonoBehaviour and public entrypoint of the Rudder Unity SDK.
  /// Put this on a startup scene object, assign a RudderConfiguration, then call
  /// <see cref="Initialize"/>. Feature calls live on the returned Core client.
  /// </summary>
  [DefaultExecutionOrder(-1500), DisallowMultipleComponent]
  public class Rudder : MonoBehaviour
  {
    private static Rudder _instance;
    private static CoreClient _client;
    private static TaskCompletionSource<CoreClient> _readyCompletion = NewCompletion();

    [SerializeField] private RudderConfiguration _configuration;

    /// Attach point for optional add-on packages so this assembly never has to
    /// reference them. Sugar over Ready: subscribers that load late must check
    /// State themselves.
    public static event Action<CoreClient> Initialized;

    public static RudderState State { get; private set; } = RudderState.NotInitialized;

    public static Exception LastError { get; private set; }

    public static CoreClient Client
    {
      get
      {
        if (State != RudderState.Ready || _client == null)
        {
          throw new InvalidOperationException(
            "Rudder is not initialized. Add the Rudder component to a scene with a " +
            "RudderConfiguration assigned, then call Rudder.Initialize().");
        }

        return _client;
      }
    }

    /// Completes with the client after <see cref="Initialize"/>, or faults on
    /// the initialization error. Prefer <see cref="Initialize"/> in game code.
    public static Task<CoreClient> Ready => _readyCompletion.Task;

    public static CoreClient Initialize()
    {
      if (State == RudderState.Ready && _client != null)
        return _client;

      if (State == RudderState.Failed)
      {
        _readyCompletion = NewCompletion();
        LastError = null;
        State = RudderState.NotInitialized;
      }

      if (_instance == null)
      {
        throw new InvalidOperationException(
          "Rudder is not in the scene. Add the Rudder component to a startup scene " +
          "and assign a RudderConfiguration.");
      }

      if (_instance._configuration == null)
      {
        throw new InvalidOperationException(
          "Rudder has no configuration. Assign a RudderConfiguration on the Rudder " +
          "component (Assets > Create > Rudder > Configuration).");
      }

      try
      {
        State = RudderState.Initializing;
        LastError = null;

        var options = new RudderUnityClientOptions
        {
          BaseUrl = _instance._configuration.BaseUrl,
          ProjectKey = _instance._configuration.ProjectKey,
          TimeoutSeconds = _instance._configuration.TimeoutSeconds,
          KeyValueStore = new PlayerPrefsUnityKeyValueStore(),
          LoggerSink = new UnityDebugLoggerSink()
        };

        _client = RudderUnityClientFactory.Create(options);
      }
      catch (Exception exception)
      {
        _client = null;
        State = RudderState.Failed;
        LastError = exception;
        _readyCompletion.TrySetException(exception);
        Debug.LogError(
          "[Rudder] Failed to initialize the Rudder SDK. Check the RudderConfiguration asset " +
          $"(ProjectKey and BaseUrl must be set). {exception.Message}");
        throw;
      }

      State = RudderState.Ready;
      Debug.Log("[Rudder] Rudder SDK initialized.");
      _readyCompletion.TrySetResult(_client);
      Initialized?.Invoke(_client);
      return _client;
    }

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
    private static void ResetStatics()
    {
      _instance = null;
      _client = null;
      State = RudderState.NotInitialized;
      LastError = null;
      Initialized = null;
      _readyCompletion = NewCompletion();
    }

    private void Awake()
    {
      if (_instance != null && _instance != this)
      {
        Destroy(gameObject);
        return;
      }

      _instance = this;
      DontDestroyOnLoad(gameObject);
    }

    private void Update()
    {
      _client?.Update(Time.deltaTime);
    }

    private void OnDestroy()
    {
      if (_instance != this)
        return;

      _instance = null;
      _client = null;
      State = RudderState.NotInitialized;
      LastError = null;
      _readyCompletion.TrySetException(new InvalidOperationException("Rudder was destroyed before initialization completed."));
      _readyCompletion = NewCompletion();
    }

    private static TaskCompletionSource<CoreClient> NewCompletion()
    {
      return new TaskCompletionSource<CoreClient>(TaskCreationOptions.RunContinuationsAsynchronously);
    }
  }
}
