using System;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Dacryptero.Data;
using Dacryptero.Data.Schema;
using LiteDB;

namespace Dacryptero.Web.Events.Receive
{
    public class DataPersonAddRequest : ReceiveBase
    {
        public string Database { get; private set; } = "";

        public string Interview { get; private set; } = "";

        public string Role { get; private set; } = "";

        public override async Task Execute(ExecuteArgs args)
        {
            var db = args.Connection.DB.GetController(Database);
            if (db is null)
            {
                await args.Connection.Send(new Send.EditDenied(Interview, null,
                    Send.EditTarget.Interview, Send.EditDeniedReason.Generic,
                    "database not found"
                )).ConfigureAwait(false);
                return;
            }
            if (!SchemaCollection.Schemas.TryGetValue(db.Config.RoleSchema, out SchemaFile? schema))
            {
                await args.Connection.Send(new Send.EditDenied(Interview, null,
                    Send.EditTarget.Interview, Send.EditDeniedReason.SchemaNotFound,
                    "schema not found"
                )).ConfigureAwait(false);
                return;
            }
            if (!schema.CombinedRoles.TryGetValue(Role, out SchemaRole? role))
            {
                await args.Connection.Send(new Send.EditDenied(Interview, null,
                    Send.EditTarget.Interview, Send.EditDeniedReason.SchemaNotFound,
                    "schema not found"
                )).ConfigureAwait(false);
                return;
            }
            var interviewId = Interview.ToObjectId();
            if (interviewId is null)
            {
                await args.Connection.Send(new Send.EditDenied(Interview, null,
                    Send.EditTarget.Interview, Send.EditDeniedReason.IdNotFound,
                    "invalid id"
                )).ConfigureAwait(false);
                return;
            }
            var interview = await db.GetInterviewAsync(interviewId, CancellationToken.None)
                .ConfigureAwait(false);
            if (interview is null)
            {
                await args.Connection.Send(new Send.EditDenied(Interview, null,
                    Send.EditTarget.Interview, Send.EditDeniedReason.IdNotFound,
                    "id does not exists"
                )).ConfigureAwait(false);
                return;
            }
            
            var now = DateTime.UtcNow;
            var person = new Person
            {
                Created = now,
                Modified = now,
                Deleted = false,
                Role = role.Name,
                ConfData = new ConfPersonData
                {
                    Contact = "",
                    Interview = interview.Value.Value,
                    Name = "",
                },
            };
            foreach (var (name, attr) in role.Attributes)
            {
                var @new = CreateAttribute(now, attr);
                @new.Key = name;
                (attr.Protected ? person.ConfData!.Attributes : person.Attributes).Add(@new);
            }
            if (!await db.AddPersonAsync(person, CancellationToken.None).ConfigureAwait(false))
                return;
            interview.Value.Value.Person.Add(person.Id);
            await db.UpdateInterviewAsync(interview.Value.Value, CancellationToken.None)
                .ConfigureAwait(false);

            await args.Connection.Send(new Send.DataPersonAddSend(person.Id))
                .ConfigureAwait(false);
        }

        private Data.Attribute CreateAttribute(DateTime now, SchemaAttribute attribute)
        {
            var recent = CreateAttributeEntry(now, attribute.Type);
            var result = new Data.Attribute
            {
                Created = now,
                Modified = now,
                RecentEntry = recent,
            };
            if (attribute.History)
                result.Entries.Add(recent);
            return result;
        }

        private AttributeEntry CreateAttributeEntry(DateTime now, SchemaAttributeType type)
        {
            return new AttributeEntry
            {
                Date = now,
                Value = type switch
                {
                    SchemaAttributeType.Bool => false,
                    SchemaAttributeType.DateTime => now,
                    SchemaAttributeType.Int => 0,
                    SchemaAttributeType.MultiLine => "",
                    SchemaAttributeType.Number => 0.0,
                    SchemaAttributeType.SingleLine => "",
                    _ => BsonValue.Null,
                }
            };
        }

        public override void ReadJsonContent(JsonElement json)
        {
            Database = json.GetProperty("database").GetString() ?? "";
            Interview = json.GetProperty("interview").GetString() ?? "";
            Role = json.GetProperty("role").GetString() ?? "";
        }
    }
}