using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
using System;
using System.Collections.Generic;
using System.Linq;

namespace PepperDash.Essentials.AppServer.Messengers
{
    /// <summary>
    /// Provides a messaging bridge
    /// </summary>
    public abstract class MessengerBase : EssentialsDevice, IMobileControlMessenger
    {
        protected IKeyName _device;

        private readonly List<string> _deviceInterfaces;

        private readonly Dictionary<string, Action<string, JToken>> _actions = new Dictionary<string, Action<string, JToken>>();

        public string DeviceKey => _device?.Key ?? "";


        /// <summary>
        /// Gets or sets the AppServerController
        /// </summary>
        public IMobileControl AppServerController { get; private set; }

        /// <summary>
        /// Gets or sets the MessagePath
        /// </summary>
        public string MessagePath { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <param name="messagePath"></param>
        protected MessengerBase(string key, string messagePath)
            : base(key)
        {
            Key = key;

            if (string.IsNullOrEmpty(messagePath))
                throw new ArgumentException("messagePath must not be empty or null");

            MessagePath = messagePath;
        }

        protected MessengerBase(string key, string messagePath, IKeyName device)
            : this(key, messagePath)
        {
            _device = device;

            _deviceInterfaces = GetInterfaces(_device as Device);
        }

        /// <summary>
        /// Gets the interfaces implmented on the device
        /// </summary>
        /// <param name="device"></param>
        /// <returns></returns>
        private List<string> GetInterfaces(Device device)
        {
            return device?.GetType().GetInterfaces().Select((i) => i.Name).ToList() ?? new List<string>();
        }

        /// <summary>
        /// Registers this messenger with appserver controller
        /// </summary>
        /// <param name="appServerController"></param>
        public void RegisterWithAppServer(IMobileControl appServerController)
        {
            AppServerController = appServerController ?? throw new ArgumentNullException("appServerController");

            AppServerController.AddAction(this, HandleMessage);

            RegisterActions();
        }

        private void HandleMessage(string path, string id, JToken content)
        {
            // replace base path with empty string. Should leave something like /fullStatus
            var route = path.Replace(MessagePath, string.Empty);

            if (!_actions.TryGetValue(route, out var action))
            {
                return;
            }

            Debug.LogMessage(Serilog.Events.LogEventLevel.Debug, "Executing action for path {path}", this, path);

            action(id, content);
        }

        protected void AddAction(string path, Action<string, JToken> action)
        {
            if (_actions.ContainsKey(path))
            {
                //Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, $"Messenger {Key} already has action registered at {path}", this);
                return;
            }

            _actions.Add(path, action);
        }

        /// <summary>
        /// GetActionPaths method
        /// </summary>
        public List<string> GetActionPaths()
        {
            return _actions.Keys.ToList();
        }

        protected void RemoveAction(string path)
        {
            if (!_actions.ContainsKey(path))
            {
                return;
            }

            _actions.Remove(path);
        }

        /// <summary>
        /// Implemented in extending classes. Wire up API calls and feedback here
        /// </summary>
        /// <param name="appServerController"></param>
        protected virtual void RegisterActions()
        {

        }

        /// <summary>
        /// Helper for posting status message
        /// </summary>
        /// <param name="type"></param>
        /// <param name="message"></param>
        protected void PostStatusMessage(DeviceStateMessageBase message, string clientId = null)
        {
            try
            {
                if (message == null)
                {
                    throw new ArgumentNullException("message");
                }

                if (_device == null)
                {
                    throw new ArgumentNullException("device");
                }

                message.SetInterfaces(_deviceInterfaces);

                message.Key = _device.Key;

                message.Name = _device.Name;

                var token = JToken.FromObject(message);                
                
                PostStatusMessage(token, MessagePath, clientId);
            }
            catch (Exception ex)
            {
                this.LogError(ex, "Exception posting status message for {messagePath} to {clientId}", MessagePath, clientId ?? "all clients");                
            }
        }

        protected void PostStatusMessage(string type, DeviceStateMessageBase deviceState, string clientId = null)
        {
            try
            {
                //Debug.Console(2, this, "*********************Setting DeviceStateMessageProperties on MobileControlResponseMessage");
                deviceState.SetInterfaces(_deviceInterfaces);

                deviceState.Key = _device.Key;

                deviceState.Name = _device.Name;

                deviceState.MessageBasePath = MessagePath;

                var token = JToken.FromObject(deviceState);

                PostStatusMessage(token, type, clientId);
            }
            catch (Exception ex)
            {
                this.LogError(ex, "Exception posting status message for {type} to {clientId}", type, clientId ?? "all clients");                
            }
        }

        protected void PostStatusMessage(JToken content, string type = "", string clientId = null)
        {
            try
            {
                AppServerController?.SendMessageObject(new MobileControlMessage { Type = !string.IsNullOrEmpty(type) ? type : MessagePath, ClientId = clientId, Content = content });
            }
            catch (Exception ex)
            {
                Debug.LogMessage(ex, "Exception posting status message", this);
            }
        }

        protected void PostEventMessage(DeviceEventMessageBase message)
        {
            message.Key = _device.Key;

            message.Name = _device.Name;

            AppServerController?.SendMessageObject(new MobileControlMessage
            {
                Type = $"/event{MessagePath}/{message.EventType}",
                Content = JToken.FromObject(message),
            });
        }

        protected void PostEventMessage(DeviceEventMessageBase message, string eventType)
        {
            message.Key = _device.Key;

            message.Name = _device.Name;

            message.EventType = eventType;

            AppServerController?.SendMessageObject(new MobileControlMessage
            {
                Type = $"/event{MessagePath}/{eventType}",
                Content = JToken.FromObject(message),
            });
        }

        protected void PostEventMessage(string eventType)
        {
            AppServerController?.SendMessageObject(new MobileControlMessage
            {
                Type = $"/event{MessagePath}/{eventType}",
                Content = JToken.FromObject(new { }),
            });
        }

    }

    public abstract class DeviceMessageBase
    {
        /// <summary>
        /// The device key
        /// </summary>
        [JsonProperty("key")]
        /// <summary>
        /// Gets or sets the Key
        /// </summary>
        public string Key { get; set; }

        /// <summary>
        /// The device name
        /// </summary>
        [JsonProperty("name")]
        public string Name { get; set; }

        /// <summary>
        /// The type of the message class
        /// </summary>
        [JsonProperty("messageType")]
        public string MessageType => GetType().Name;

        [JsonProperty("messageBasePath")]
        /// <summary>
        /// Gets or sets the MessageBasePath
        /// </summary>
        public string MessageBasePath { get; set; }
    }

    /// <summary>
    /// Represents a DeviceStateMessageBase
    /// </summary>
    public class DeviceStateMessageBase : DeviceMessageBase
    {
        /// <summary>
        /// The interfaces implmented by the device sending the messsage
        /// </summary>
        [JsonProperty("interfaces")]
        public List<string> Interfaces { get; private set; }

        public void SetInterfaces(List<string> interfaces)
        {
            Interfaces = interfaces;
        }
    }

    /// <summary>
    /// Base class for event messages that include the type of message and an event type
    /// </summary>
    public abstract class DeviceEventMessageBase : DeviceMessageBase
    {
        /// <summary>
        /// The event type
        /// </summary>
        [JsonProperty("eventType")]
        public string EventType { get; set; }
    }

}