using Csla;
using Csla.Core;
using Csla.Rules;
using SyncfusionSchedulerDeleteBug.DataAccess.Events;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Text;

namespace SyncfusionSchedulerDeleteBug.Business.Events
{
    [Serializable]
    public class CalendarEventEdit : BusinessBase<CalendarEventEdit>
    {
        #region events

        public event Action OnIsTimezoneChanged = delegate { };

        #endregion

        #region properties     

        public static readonly PropertyInfo<byte[]> TimeStampProperty = RegisterProperty<byte[]>(nameof(TimeStamp));
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public byte[] TimeStamp
        {
            get { return GetProperty(TimeStampProperty); }
            set { SetProperty(TimeStampProperty, value); }
        }

        public readonly static PropertyInfo<int> IdProperty = RegisterProperty<int>(nameof(Id));
        public int Id
        {
            get { return GetProperty(IdProperty); }
            private set { LoadProperty(IdProperty, value); }
        }

        public readonly static PropertyInfo<int> CompanyIdProperty = RegisterProperty<int>(nameof(CompanyId));
        [Range(1, Int32.MaxValue, ErrorMessage = "Company is required")]
        public int CompanyId
        {
            get { return GetProperty(CompanyIdProperty); }
            set 
            { 
                SetProperty(CompanyIdProperty, value);                
            }
        }

        public readonly static PropertyInfo<string> CompanyNameProperty = RegisterProperty<string>(nameof(CompanyName));
        [MaxLength(50, ErrorMessage = "Company name has a maximum length of 50 characters")]
        public string CompanyName
        {
            get { return GetProperty(CompanyNameProperty); }
            set 
            { 
                SetProperty(CompanyNameProperty, value); 

            }
        }

        public readonly static PropertyInfo<int> CategoryIdProperty = RegisterProperty<int>(nameof(CategoryId));
        [Range(1, Int32.MaxValue, ErrorMessage = "Event category is required")]
        public int CategoryId
        {
            get { return GetProperty(CategoryIdProperty); }
            set { SetProperty(CategoryIdProperty, value); }
        }

        public readonly static PropertyInfo<int> WorkerIdProperty = RegisterProperty<int>(nameof(WorkerId));
        [Range(1, Int32.MaxValue, ErrorMessage = "Worker is required")]
        public int WorkerId
        {
            get { return GetProperty(WorkerIdProperty); }
            set { SetProperty(WorkerIdProperty, value); }
        }

        public readonly static PropertyInfo<int> CountryIdProperty = RegisterProperty<int>(nameof(CountryId));        
        public int CountryId
        {
            get { return GetProperty(CountryIdProperty); }
            set { SetProperty(CountryIdProperty, value); }
        }

        public readonly static PropertyInfo<int> ProjectIdProperty = RegisterProperty<int>(nameof(ProjectId));
        public int ProjectId
        {
            get { return GetProperty(ProjectIdProperty); }
            set { SetProperty(ProjectIdProperty, value); }
        }

        public readonly static PropertyInfo<int?> PayrollIdProperty = RegisterProperty<int?>(nameof(PayrollId));
        public int? PayrollId
        {
            get { return GetProperty(PayrollIdProperty); }
            set { SetProperty(PayrollIdProperty, value); }
        }

        public readonly static PropertyInfo<bool> ProjectPayrollEmptyProperty = RegisterProperty<bool>(nameof(ProjectPayrollEmpty));
        public bool ProjectPayrollEmpty
        {
            get { return GetProperty(ProjectPayrollEmptyProperty); }
            set { SetProperty(ProjectPayrollEmptyProperty, value); }
        }

        //if project selected and payroll id isn't set
        public bool CanEditPayrollId => ProjectId > 0 && ProjectPayrollEmpty;

        public readonly static PropertyInfo<int> VisaIdProperty = RegisterProperty<int>(nameof(VisaId));
        public int VisaId
        {
            get { return GetProperty(VisaIdProperty); }
            set { SetProperty(VisaIdProperty, value); }
        }

        public readonly static PropertyInfo<string> ProjectNameProperty = RegisterProperty<string>(nameof(ProjectName));
        [MaxLength(60, ErrorMessage = "Project name has a maximum length of 60 characters")]
        public string ProjectName
        {
            get { return GetProperty(ProjectNameProperty); }
            set { SetProperty(ProjectNameProperty, value); }
        }

        //Notes
        public readonly static PropertyInfo<string> SummaryProperty = RegisterProperty<string>(nameof(Summary));
        [Required(ErrorMessage = "Summary is required")]
        public string Summary
        {
            get { return GetProperty(SummaryProperty); }
            set { SetProperty(SummaryProperty, value); }
        }

        public readonly static PropertyInfo<string> LocationProperty = RegisterProperty<string>(nameof(Location));
        public string Location
        {
            get { return GetProperty(LocationProperty); }
            set { SetProperty(LocationProperty, value); }
        }

        public readonly static PropertyInfo<bool> IsAllDayProperty = RegisterProperty<bool>(nameof(IsAllDay));
        public bool IsAllDay
        {
            get { return GetProperty(IsAllDayProperty); }
            set { SetProperty(IsAllDayProperty, value); }
        }

        public readonly static PropertyInfo<bool> IsTimeZoneProperty = RegisterProperty<bool>(nameof(IsTimeZone));
        public bool IsTimeZone
        {
            get { return GetProperty(IsTimeZoneProperty); }
            set { SetProperty(IsTimeZoneProperty, value); }
        }

        public bool IsRecurring => string.IsNullOrEmpty(RecurrenceRule) == false;

        public readonly static PropertyInfo<int?> RecurrenceIDProperty = RegisterProperty<int?>(nameof(RecurrenceID));
        public int? RecurrenceID
        {
            get { return GetProperty(RecurrenceIDProperty); }
            set { SetProperty(RecurrenceIDProperty, value); }
        }

        public readonly static PropertyInfo<string> RecurrenceRuleProperty = RegisterProperty<string>(nameof(RecurrenceRule));
        public string RecurrenceRule
        {
            get { return GetProperty(RecurrenceRuleProperty); }
            set { SetProperty(RecurrenceRuleProperty, value); }
        }

        public readonly static PropertyInfo<string> RecurrenceExceptionProperty = RegisterProperty<string>(nameof(RecurrenceException));
        public string RecurrenceException
        {
            get { return GetProperty(RecurrenceExceptionProperty); }
            set { SetProperty(RecurrenceExceptionProperty, value); }
        }

        public readonly static PropertyInfo<DateTime?> LastOccurenceDateProperty = RegisterProperty<DateTime?>(nameof(LastOccurenceDate));
        public DateTime? LastOccurenceDate
        {
            get { return GetProperty(LastOccurenceDateProperty); }
            set { SetProperty(LastOccurenceDateProperty, value); }
        }

        public readonly static PropertyInfo<DateTimeOffset> DStartProperty = RegisterProperty<DateTimeOffset>(nameof(DStart));
        public DateTimeOffset DStart
        {
            get { return GetProperty(DStartProperty); }
            set { SetProperty(DStartProperty, value); }
        }

        public readonly static PropertyInfo<DateTimeOffset> DEndProperty = RegisterProperty<DateTimeOffset>(nameof(DEnd));
        public DateTimeOffset DEnd
        {
            get { return GetProperty(DEndProperty); }
            set { SetProperty(DEndProperty, value); }
        }       

        public readonly static PropertyInfo<string> StartTimezoneIdProperty = RegisterProperty<string>(nameof(StartTimezoneId));
        public string StartTimezoneId
        {
            get { return GetProperty(StartTimezoneIdProperty); }
            set { SetProperty(StartTimezoneIdProperty, value); }
        }

        public readonly static PropertyInfo<string> EndTimezoneIdProperty = RegisterProperty<string>(nameof(EndTimezoneId));
        public string EndTimezoneId
        {
            get { return GetProperty(EndTimezoneIdProperty); }
            set { SetProperty(EndTimezoneIdProperty, value); }
        }

        public readonly static PropertyInfo<string> WorkerDefaultTimezoneIdProperty = RegisterProperty<string>(nameof(WorkerDefaultTimezoneId));
        public string WorkerDefaultTimezoneId
        {
            get { return GetProperty(WorkerDefaultTimezoneIdProperty); }
            set { LoadProperty(WorkerDefaultTimezoneIdProperty, value); }
        }

        public readonly static PropertyInfo<TimeSpan?> UserOffsetProperty = RegisterProperty<TimeSpan?>(nameof(UserOffset));
        public TimeSpan? UserOffset
        {
            get { return GetProperty(UserOffsetProperty); }
            set { LoadProperty(UserOffsetProperty, value); }
        }

        private bool ApplyingDefault { get; set; }

        #endregion

        #region business methods

        protected override void PropertyHasChanged(IPropertyInfo propertyInfo)
        {
            base.PropertyHasChanged(propertyInfo);

            if (propertyInfo == CompanyIdProperty)
            {
                SetProperty(ProjectIdProperty, 0);
                SetProperty(CountryIdProperty, 0);

            }
            else if (propertyInfo == ProjectIdProperty)
            {
                SetProperty(CountryIdProperty, 0);
            }

            if (propertyInfo == IsTimeZoneProperty)
            {
                applyTimeZoneToStartDate();
                applyTimeZoneToEndDate();

                if (!ApplyingDefault)
                    OnIsTimezoneChanged();
            }

            if (propertyInfo == StartTimezoneIdProperty || propertyInfo == DStartProperty)
            {
                applyTimeZoneToStartDate();
            }
            else if (propertyInfo == EndTimezoneIdProperty || propertyInfo == DEndProperty)
            {
                applyTimeZoneToEndDate();
            }
        }

        public void ApplyWorkerDefaultTimezone()
        {
            if (!string.IsNullOrEmpty(WorkerDefaultTimezoneId))
            {
                ApplyingDefault = true;

                StartTimezoneId = WorkerDefaultTimezoneId;
                EndTimezoneId = WorkerDefaultTimezoneId;

                IsTimeZone = true;

                ApplyingDefault = false;
            }
        }

        private void applyTimeZoneToStartDate()
        {
            //timezones required rule
            if (IsTimeZone)
            {
                if (!string.IsNullOrEmpty(StartTimezoneId))
                {
                    DStart = DStart.DateTime.ApplyOffsetByTimezoneId(StartTimezoneId);

                    if (string.IsNullOrEmpty(EndTimezoneId))
                    {
                        EndTimezoneId = StartTimezoneId;
                    }
                }
            }
            else
            {
                DStart = DStart.DateTime.ApplyOffset(UserOffset.GetValueOrDefault());
            }
        }

        private void applyTimeZoneToEndDate()
        {
            if (IsTimeZone)
            {
                if (!string.IsNullOrEmpty(EndTimezoneId))
                {
                    DEnd = DEnd.DateTime.ApplyOffsetByTimezoneId(EndTimezoneId);

                    if (string.IsNullOrEmpty(StartTimezoneId))
                    {
                        StartTimezoneId = EndTimezoneId;
                    }
                }
            }
            else
            {
                DEnd = DEnd.DateTime.ApplyOffset(UserOffset.GetValueOrDefault());
            }
        }

        public void SetProjectPayroll(int? payrollid)
        {
            ProjectPayrollEmpty = payrollid.GetValueOrDefault() == 0;
            PayrollId = payrollid;

            OnPropertyChanged(nameof(CanEditPayrollId));
        }

        public void MarkClean()
        {
            base.MarkClean();
        }
               
        #endregion

        #region business rules

        protected override void AddBusinessRules()
        {
            base.AddBusinessRules();

            BusinessRules.AddRule(new CountryRequired
            {
                PrimaryProperty = CountryIdProperty                
            });

            BusinessRules.AddRule(new TimezonesRequired(StartTimezoneIdProperty, IsTimeZoneProperty));
            BusinessRules.AddRule(new TimezonesRequired(EndTimezoneIdProperty, IsTimeZoneProperty));
        }

        private class CountryRequired : Csla.Rules.BusinessRule
        {
            protected override void Execute(Csla.Rules.IRuleContext context)
            {
                var target = (CalendarEventEdit)context.Target;

                var country = target.ReadProperty(CountryIdProperty);
                
                if (country == 0)
                    context.AddErrorResult("Country is required");
            }
        }

        private class TimezonesRequired : Csla.Rules.BusinessRule
        {
            private readonly IPropertyInfo timezoneProperty;
            private readonly IPropertyInfo requiredProperty;

            public TimezonesRequired(IPropertyInfo timezone, IPropertyInfo required) : base(timezone)
            {
                this.timezoneProperty = timezone;
                this.requiredProperty = required;

                InputProperties = new List<IPropertyInfo>() { timezone, required };
            }

            protected override void Execute(IRuleContext context)
            {
                bool required = context.GetInputValue<bool>(requiredProperty);

                if (required)
                {
                    string timezone = context.GetInputValue<string>(timezoneProperty);

                    if (string.IsNullOrEmpty(timezone))
                        context.AddErrorResult("Timezone is required");
                }
                else
                    context.AddSuccessResult(false);
            }
        }

        public void AddOccurenceException(DateTime occurencedate)
        {
            string formattedDate = occurencedate.ToString("yyyyMMddTHHmmssZ");

            AddOccurenceException(formattedDate);
        }

        public void AddOccurenceException(string formattedDate)
        {
            if (string.IsNullOrEmpty(RecurrenceException))
                RecurrenceException = formattedDate;
            else
            {
                StringBuilder builder = new StringBuilder(RecurrenceException);
                builder.Append(",");
                builder.Append(formattedDate);

                RecurrenceException = builder.ToString();
            }
        }

        public DateTime? CalculateLastOccurence()
        {
            if (string.IsNullOrEmpty(RecurrenceRule))
                return null;

            RecurrenceHelper helper = new RecurrenceHelper();
            return helper.GetLastOccurrence(RecurrenceRule, DStart.LocalDateTime, RecurrenceException);
        }

        #endregion

        #region data access

        [Create]
        private void Create(CalendarEventCreateCriteria criteria)
        {
            using (BypassPropertyChecks)
            {
                DStart = criteria.StartDate;
                DEnd = criteria.EndDate;

                CategoryId = (int)CategoryType.Travel;
                WorkerId = criteria.WorkerId;
                WorkerDefaultTimezoneId = criteria.WorkerDefaultTimezoneId;
                CompanyId = criteria.CompanyId;
                ProjectId = criteria.ProjectId;
                CountryId = criteria.CountryId;
                UserOffset = criteria.UserOffset;

                ApplyWorkerDefaultTimezone();
            }

            BusinessRules.CheckRules();
        }

        [Create]
        private void Create(CalendarEventEdit parent)
        {
            using (BypassPropertyChecks)
            {
                Id = 0;
                RecurrenceID = parent.Id;
                RecurrenceRule = string.Empty;
                RecurrenceException = string.Empty;

                //copy details from parent
                WorkerId = parent.WorkerId;
                WorkerDefaultTimezoneId = parent.WorkerDefaultTimezoneId;
                CategoryId = parent.CategoryId;
                CompanyId = parent.CompanyId;
                CompanyName = parent.CompanyName;
                ProjectId = parent.ProjectId;
                ProjectName = parent.ProjectName;
                CountryId = parent.CountryId;
                VisaId = parent.VisaId;
                Location = parent.Location;
                Summary = parent.Summary;
                IsAllDay = parent.IsAllDay;
                IsTimeZone = parent.IsTimeZone;
                DStart = parent.DStart;
                DEnd = parent.DEnd;
                StartTimezoneId = parent.StartTimezoneId;
                EndTimezoneId = parent.EndTimezoneId;
            }

            BusinessRules.CheckRules();
        }

        [Insert]
        private void Insert([Inject] IEventsDal dal)
        {
            CalendarEventEditDto dto = new CalendarEventEditDto()
            {
                WorkerId = this.WorkerId,
                CategoryId = this.CategoryId,
                CompanyId = this.CompanyId,
                CompanyName = this.CompanyName,
                ProjectId = this.ProjectId,
                ProjectName = this.ProjectName,
                PayrollId = this.PayrollId,
                CountryId = this.CountryId,
                VisaId = this.VisaId,
                Summary = this.Summary,
                Location = this.Location,
                IsAllDay = this.IsAllDay,
                IsTimeZone = this.IsTimeZone,
                IsRecurring = this.IsRecurring,
                RecurrenceRule = this.RecurrenceRule.ToUpper(),
                RecurrenceId = this.RecurrenceID,
                LastOccurenceDate = CalculateLastOccurence(),
                StartTimezoneId = this.StartTimezoneId,
                EndTimezoneId = this.EndTimezoneId,
                DStart = this.DStart,
                DEnd = this.DEnd,
                CreatedOn = DateTime.Now,
                CreatedBy = Csla.ApplicationContext.User.Identity.Name
            };

            dal.Insert(dto);

            Id = dto.Id;
            TimeStamp = dto.LastChanged;
        }

        [Fetch]
        private void Fetch(int eventid, [Inject] IEventsDal dal)
        {
            CalendarEventEditDto dto = dal.Fetch(eventid);
            if (dto is null)
                return;

            using (BypassPropertyChecks)
            {
                Id = dto.Id;

                WorkerId = dto.WorkerId;
                WorkerDefaultTimezoneId = dto.WorkerDefaultTimezoneId;
                CategoryId = dto.CategoryId;
                CompanyId = dto.CompanyId;
                CompanyName = dto.CompanyName;
                ProjectId = dto.ProjectId;
                ProjectName = dto.ProjectName;
                PayrollId = dto.PayrollId;
                CountryId = dto.CountryId;
                VisaId = dto.VisaId;
                Location = dto.Location;
                Summary = dto.Summary;
                IsAllDay = dto.IsAllDay;
                IsTimeZone = dto.IsTimeZone;
                DStart = dto.DStart;
                DEnd = dto.DEnd;
                RecurrenceRule = dto.RecurrenceRule;
                RecurrenceException = dto.RecurrenceException;
                RecurrenceID = dto.RecurrenceId;
                LastOccurenceDate = dto.LastOccurenceDate;
                StartTimezoneId = dto.StartTimezoneId;
                EndTimezoneId = dto.EndTimezoneId;
                TimeStamp = dto.LastChanged;
            }
        }

        [Update]
        private void Update([Inject] IEventsDal dal)
        {            
            CalendarEventEditDto dto = new CalendarEventEditDto()
            {
                Id = this.Id,
                WorkerId = this.WorkerId,
                CategoryId = this.CategoryId,
                CompanyId = this.CompanyId,
                CompanyName = this.CompanyName,
                ProjectId = this.ProjectId,
                ProjectName = this.ProjectName,
                PayrollId = this.PayrollId,
                CountryId = this.CountryId,
                VisaId = this.VisaId,
                Summary = this.Summary,
                Location = this.Location,
                IsAllDay = this.IsAllDay,
                IsTimeZone = this.IsTimeZone,
                IsRecurring = this.IsRecurring,
                RecurrenceRule = this.RecurrenceRule.ToUpper(),
                RecurrenceException = this.RecurrenceException,
                RecurrenceId = this.RecurrenceID,
                LastOccurenceDate = CalculateLastOccurence(),
                StartTimezoneId = this.StartTimezoneId,
                EndTimezoneId = this.EndTimezoneId,
                DStart = this.DStart,
                DEnd = this.DEnd,
                ModifiedDate = DateTime.Now,
                ModifiedBy = Csla.ApplicationContext.User.Identity.Name,
                LastChanged = this.TimeStamp
            };

            dal.Update(dto);

            TimeStamp = dto.LastChanged;
        }

        [Delete]
        private void Delete(int eventId, [Inject] IEventsDal dal)
        {
            dal.Delete(eventId);
        }

        #endregion
    }
}
