using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;

namespace XLAB
{
    internal sealed class PrfrEditWindowViewModel : ObservableObject
    {
        private readonly IReadOnlyList<PrfrDirectoryItem> _existingItems;
        private string _guid;
        private bool? _isPinAuth;
        private int _organizationId;
        private string _personnelNumber;
        private string _pinChangedAtText;
        private string _pinHash;
        private int _positionId;
        private string _validationMessage;

        public PrfrEditWindowViewModel(PrfrDirectoryItem seed, bool isNew, IReadOnlyList<PrfrDirectoryItem> existingItems, PrsnDirectoryService service)
        {
            var source = seed ?? new PrfrDirectoryItem();
            _existingItems = existingItems ?? Array.Empty<PrfrDirectoryItem>();
            EmploymentId = source.EmploymentId;
            PersonId = source.PersonId;
            IsNew = isNew;

            OrganizationItems = service.LoadFrpdReferences();
            PositionItems = new[] { new DirectoryLookupItem { Id = 0, Name = string.Empty } }
                .Concat(service.LoadSpdlReferences() ?? Array.Empty<DirectoryLookupItem>())
                .ToList();

            OrganizationId = source.OrganizationId;
            PositionId = source.PositionId.HasValue ? source.PositionId.Value : 0;
            HireDate = source.HireDate;
            DismissalDate = source.DismissalDate;
            PositionStartDate = source.PositionStartDate;
            QualificationPlanDate = source.QualificationPlanDate;
            LastVacationStartDate = source.LastVacationStartDate;
            LastVacationEndDate = source.LastVacationEndDate;
            NextVacationStartDate = source.NextVacationStartDate;
            NextVacationEndDate = source.NextVacationEndDate;
            PersonnelNumber = source.PersonnelNumber ?? string.Empty;
            Guid = source.Guid ?? string.Empty;
            PinHash = source.PinHash ?? string.Empty;
            PinChangedAtText = source.PinChangedAt.HasValue ? source.PinChangedAt.Value.ToString("dd.MM.yyyy HH:mm") : string.Empty;
            IsPinAuth = source.IsPinAuth;

            ConfirmCommand = new RelayCommand(Confirm);
            CancelCommand = new RelayCommand(Cancel);
        }

        public event EventHandler<bool?> CloseRequested;

        public ICommand CancelCommand { get; private set; }
        public ICommand ConfirmCommand { get; private set; }
        public DateTime? DismissalDate { get; set; }
        public int EmploymentId { get; private set; }

        public string Guid
        {
            get { return _guid; }
            set { SetProperty(ref _guid, value); }
        }

        public DateTime? HireDate { get; set; }

        public bool? IsPinAuth
        {
            get { return _isPinAuth; }
            set { SetProperty(ref _isPinAuth, value); }
        }

        public bool IsNew { get; private set; }
        public DateTime? LastVacationEndDate { get; set; }
        public DateTime? LastVacationStartDate { get; set; }
        public DateTime? NextVacationEndDate { get; set; }
        public DateTime? NextVacationStartDate { get; set; }

        public int OrganizationId
        {
            get { return _organizationId; }
            set { SetProperty(ref _organizationId, value); }
        }

        public IReadOnlyList<DirectoryLookupItem> OrganizationItems { get; private set; }

        public int PersonId { get; private set; }

        public string PersonnelNumber
        {
            get { return _personnelNumber; }
            set { SetProperty(ref _personnelNumber, value); }
        }

        public string PinChangedAtText
        {
            get { return _pinChangedAtText; }
            set { SetProperty(ref _pinChangedAtText, value); }
        }

        public string PinHash
        {
            get { return _pinHash; }
            set { SetProperty(ref _pinHash, value); }
        }

        public int PositionId
        {
            get { return _positionId; }
            set { SetProperty(ref _positionId, value); }
        }

        public IReadOnlyList<DirectoryLookupItem> PositionItems { get; private set; }
        public DateTime? PositionStartDate { get; set; }
        public DateTime? QualificationPlanDate { get; set; }

        public string Title
        {
            get { return IsNew ? "Новая запись персонала в организации" : "Редактирование записи персонала в организации"; }
        }

        public string ValidationMessage
        {
            get { return _validationMessage; }
            private set { SetProperty(ref _validationMessage, value); }
        }

        public PrfrDirectoryItem ToResult()
        {
            return new PrfrDirectoryItem
            {
                DismissalDate = DismissalDate,
                EmploymentId = EmploymentId,
                Guid = NormalizeNullable(Guid),
                HireDate = HireDate,
                IsPinAuth = IsPinAuth,
                LastVacationEndDate = LastVacationEndDate,
                LastVacationStartDate = LastVacationStartDate,
                NextVacationEndDate = NextVacationEndDate,
                NextVacationStartDate = NextVacationStartDate,
                OrganizationId = OrganizationId,
                PersonId = PersonId,
                PersonnelNumber = NormalizeNullable(PersonnelNumber),
                PinChangedAt = ParseNullableDateTime(PinChangedAtText),
                PinHash = NormalizeNullable(PinHash),
                PositionId = PositionId > 0 ? (int?)PositionId : null,
                PositionStartDate = PositionStartDate,
                QualificationPlanDate = QualificationPlanDate
            };
        }

        private void Cancel(object parameter)
        {
            RaiseCloseRequested(false);
        }

        private void Confirm(object parameter)
        {
            var normalizedPersonnelNumber = NormalizeNullable(PersonnelNumber);
            var normalizedGuid = NormalizeNullable(Guid);
            DateTime? pinChangedAt;

            if (OrganizationId <= 0)
            {
                ValidationMessage = "Укажите организацию/подразделение.";
                return;
            }

            if (normalizedPersonnelNumber != null && normalizedPersonnelNumber.Length > PrfrDirectoryRules.PersonnelNumberMaxLength)
            {
                ValidationMessage = string.Format("Табельный номер не должен превышать {0} символов.", PrfrDirectoryRules.PersonnelNumberMaxLength);
                return;
            }

            if (normalizedGuid != null && normalizedGuid.Length > PrfrDirectoryRules.GuidMaxLength)
            {
                ValidationMessage = string.Format("GUID сотрудника не должен превышать {0} символов.", PrfrDirectoryRules.GuidMaxLength);
                return;
            }

            if (!TryParseNullableDateTime(PinChangedAtText, out pinChangedAt))
            {
                ValidationMessage = "Дата/время изменения PIN указаны в неверном формате.";
                return;
            }

            var duplicate = _existingItems.FirstOrDefault(delegate(PrfrDirectoryItem item)
            {
                return item != null
                       && item.EmploymentId != EmploymentId
                       && item.OrganizationId == OrganizationId;
            });
            if (duplicate != null)
            {
                ValidationMessage = "Такая связка персоны и организации уже существует в справочнике.";
                return;
            }

            ValidationMessage = string.Empty;
            RaiseCloseRequested(true);
        }

        private string NormalizeNullable(string value)
        {
            return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
        }

        private DateTime? ParseNullableDateTime(string value)
        {
            DateTime? result;
            return TryParseNullableDateTime(value, out result) ? result : null;
        }

        private bool TryParseNullableDateTime(string value, out DateTime? result)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                result = null;
                return true;
            }

            DateTime parsedValue;
            if (DateTime.TryParse(value.Trim(), out parsedValue))
            {
                result = parsedValue;
                return true;
            }

            result = null;
            return false;
        }

        private void RaiseCloseRequested(bool? dialogResult)
        {
            var handler = CloseRequested;
            if (handler != null)
            {
                handler(this, dialogResult);
            }
        }
    }
}
