using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using Aurora.Proto.Party;
using Aurora.Proto.General;
using Aurora.Utils;
using Grpc.Core;
using Google.Protobuf.WellKnownTypes;

namespace Aurora.Services.Server.Controllers
{
    public partial class RemotePartyController : RemotePartyService.RemotePartyServiceBase
    {
        private SortedList<string, Member> _memberList;

        public override Task<ListMembersResponse> ListMembers(ListMembersRequest request, Grpc.Core.ServerCallContext context)
        {
            //Ignoring parent field because there is only one instance of the party
            ListMembersResponse resp = new ListMembersResponse();

            //Determine start idx
            int startIdx = 0;
            if (!string.IsNullOrEmpty(request.PageToken))
            {
                startIdx = _memberList.IndexOfKey(request.PageToken) + 1;
            }

            int pageSize = request.PageSize;

            //Assign pageSize
            if (pageSize > _memberList.Count)
            {
                pageSize = _memberList.Count;
            }


            //Gather page
            List<Member> members = new List<Member>(_memberList.Values);
            resp.Members.AddRange(members.GetRange(startIdx, pageSize));

            //Set next page token
            resp.NextPageToken = resp.Members[resp.Members.Count - 1].Name;

            return Task.FromResult(resp);
        }

        public override Task<Member> GetMember(GetMemberRequest request, Grpc.Core.ServerCallContext context)
        {
            _memberList.TryGetValue(request.Name, out Member member);

            if (member == null)
            {
                throw new KeyNotFoundException();
            }

            return Task.FromResult(member);
        }

        public override Task<Member> UpdateMember(UpdateMemberRequest request, Grpc.Core.ServerCallContext context)
        {
            throw new NotImplementedException();
        }

        public override Task<Member> CreateMember(CreateMemberRequest request, Grpc.Core.ServerCallContext context)
        {
            //Generate Guid
            string resourceName = GetNewMemberResourceName(request.Parent, context.Peer, request.Member.UserName);

            //Check if already added
            if (_memberList.ContainsKey(resourceName))
            {
                throw new RpcException(new Status(StatusCode.AlreadyExists, "Member already exists"));
            }

            request.Member.Name = resourceName;
            request.Member.AddedOn = Timestamp.FromDateTime(DateTime.UtcNow);
            request.Member.IpAddress = context.Host;

            _memberList.Add(resourceName, request.Member);

            BaseEvent @event = new BaseEvent
            {
                EventType = EventType.MemberCreated,
                MemberCreatedEvent = new MemberCreatedEvent
                {
                    Member = request.Member,
                }
            };

            //Fire event manager event
            this._eventManager.FireEvent(@event);

            return Task.FromResult(request.Member);
        }

        public override Task<Aurora.Proto.General.Empty> DeleteMember(DeleteMemberRequest request, Grpc.Core.ServerCallContext context)
        {
            string memberResourceName = request.Name;
            //Check if member exists
            if (!_memberList.ContainsKey(request.Name))
            {
                throw new RpcException(new Status(StatusCode.NotFound, "Member not found"));
            }

            _memberList.Remove(memberResourceName);

            BaseEvent @event = new BaseEvent
            {
                EventType = EventType.MemberDeleted,
                MemberDeletedEvent = new MemberDeletedEvent
                {
                    MemberName = memberResourceName,
                }
            };

            _eventManager.FireEvent(@event);
            _eventManager.RemoveAllSubscriptions(memberResourceName);
            _eventManager.CancelEventStream(memberResourceName);

            return Task.FromResult(new Aurora.Proto.General.Empty());
        }

        private string GetNewMemberResourceName(string parent, string contextPeer, string userName)
        {
            string memberNameGuid = HashUtil.GetHash(new string[] { contextPeer, userName }).ToString();
            return string.Format("{0}/members/{1}", parent, memberNameGuid);
        }
    }
}