using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Serilog;
using Wino.Calendar.ViewModels.Data;
using Wino.Calendar.ViewModels.Interfaces;
using Wino.Core.Domain.Collections;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Extensions;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Calendar;
using Wino.Core.Domain.Models.Navigation;
using Wino.Core.Domain.Models.Synchronization;
using Wino.Core.ViewModels;
using Wino.Messaging.Client.Calendar;
using Wino.Messaging.Client.Navigation;
using Wino.Messaging.Server;

namespace Wino.Calendar.ViewModels;

public partial class AppShellViewModel : CalendarBaseViewModel,
    IRecipient<VisibleDateRangeChangedMessage>,
    IRecipient<CalendarEnableStatusChangedMessage>,
    IRecipient<NavigateManageAccountsRequested>,
    IRecipient<CalendarDisplayTypeChangedMessage>,
    IRecipient<DetailsPageStateChangedMessage>
{
    public IPreferencesService PreferencesService { get; }
    public IStatePersistanceService StatePersistenceService { get; }
    public IAccountCalendarStateService AccountCalendarStateService { get; }
    public INavigationService NavigationService { get; }

    [ObservableProperty]
    private bool _isEventDetailsPageActive;

    [ObservableProperty]
    private int _selectedMenuItemIndex = -1;

    [ObservableProperty]
    private bool isCalendarEnabled;



    /// <summary>
    /// Gets or sets the display date of the calendar.
    /// </summary>
    [ObservableProperty]
    private DateTimeOffset _displayDate;

    /// <summary>
    /// Gets or sets the highlighted range in the CalendarView and displayed date range in FlipView.
    /// </summary>
    [ObservableProperty]
    private DateRange highlightedDateRange;

    [ObservableProperty]
    private ObservableRangeCollection<string> dateNavigationHeaderItems = [];

    [ObservableProperty]
    private int _selectedDateNavigationHeaderIndex;

    public bool IsVerticalCalendar => StatePersistenceService.CalendarDisplayType == CalendarDisplayType.Month;

    // For updating account calendars asynchronously.
    private SemaphoreSlim _accountCalendarUpdateSemaphoreSlim = new(1);

    public AppShellViewModel(IPreferencesService preferencesService,
                             IStatePersistanceService statePersistanceService,
                             IAccountService accountService,
                             ICalendarService calendarService,
                             IAccountCalendarStateService accountCalendarStateService,
                             INavigationService navigationService)
    {
        _accountService = accountService;
        _calendarService = calendarService;

        AccountCalendarStateService = accountCalendarStateService;
        AccountCalendarStateService.AccountCalendarSelectionStateChanged += UpdateAccountCalendarRequested;
        AccountCalendarStateService.CollectiveAccountGroupSelectionStateChanged += AccountCalendarStateCollectivelyChanged;

        NavigationService = navigationService;
        PreferencesService = preferencesService;

        StatePersistenceService = statePersistanceService;
        StatePersistenceService.StatePropertyChanged += PrefefencesChanged;
    }

    private void SelectedCalendarItemsChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        throw new NotImplementedException();
    }

    private void PrefefencesChanged(object sender, string e)
    {
        if (e == nameof(StatePersistenceService.CalendarDisplayType))
        {
            Messenger.Send(new CalendarDisplayTypeChangedMessage(StatePersistenceService.CalendarDisplayType));


            // Change the calendar.
            DateClicked(new CalendarViewDayClickedEventArgs(GetDisplayTypeSwitchDate()));
        }
    }

    public override async void OnNavigatedTo(NavigationMode mode, object parameters)
    {
        base.OnNavigatedTo(mode, parameters);

        UpdateDateNavigationHeaderItems();

        await InitializeAccountCalendarsAsync();

        TodayClicked();
    }

    private async void AccountCalendarStateCollectivelyChanged(object sender, GroupedAccountCalendarViewModel e)
    {
        // When using three-state checkbox, multiple accounts will be selected/unselected at the same time.
        // Reporting all these changes one by one to the UI is not efficient and may cause problems in the future.

        // Update all calendar states at once.
        try
        {
            await _accountCalendarUpdateSemaphoreSlim.WaitAsync();

            foreach (var calendar in e.AccountCalendars)
            {
                await _calendarService.UpdateAccountCalendarAsync(calendar.AccountCalendar).ConfigureAwait(false);
            }
        }
        catch (Exception ex)
        {
            Log.Error(ex, "Error while waiting for account calendar update semaphore.");
        }
        finally
        {
            _accountCalendarUpdateSemaphoreSlim.Release();
        }
    }

    private async void UpdateAccountCalendarRequested(object sender, AccountCalendarViewModel e)
        => await _calendarService.UpdateAccountCalendarAsync(e.AccountCalendar).ConfigureAwait(false);

    private async Task InitializeAccountCalendarsAsync()
    {
        await Dispatcher.ExecuteOnUIThread(() => AccountCalendarStateService.ClearGroupedAccountCalendar());

        var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);

        foreach (var account in accounts)
        {
            var accountCalendars = await _calendarService.GetAccountCalendarsAsync(account.Id).ConfigureAwait(false);
            var calendarViewModels = new List<AccountCalendarViewModel>();

            foreach (var calendar in accountCalendars)
            {
                var calendarViewModel = new AccountCalendarViewModel(account, calendar);

                calendarViewModels.Add(calendarViewModel);
            }

            var groupedAccountCalendarViewModel = new GroupedAccountCalendarViewModel(account, calendarViewModels);

            await Dispatcher.ExecuteOnUIThread(() =>
            {
                AccountCalendarStateService.AddGroupedAccountCalendar(groupedAccountCalendarViewModel);
            });
        }
    }

    private void ForceNavigateCalendarDate()
    {
        if (SelectedMenuItemIndex == -1)
        {
            var args = new CalendarPageNavigationArgs()
            {
                NavigationDate = _navigationDate ?? DateTime.Now.Date
            };

            // Already on calendar. Just navigate.
            NavigationService.Navigate(WinoPage.CalendarPage, args);

            _navigationDate = null;
        }
        else
        {
            SelectedMenuItemIndex = -1;
        }
    }

    partial void OnSelectedMenuItemIndexChanged(int oldValue, int newValue)
    {
        switch (newValue)
        {
            case -1:
                ForceNavigateCalendarDate();
                break;
            case 0:
                NavigationService.Navigate(WinoPage.ManageAccountsPage);
                break;
            case 1:
                NavigationService.Navigate(WinoPage.SettingsPage);
                break;
            default:
                break;
        }
    }

    [RelayCommand]
    private async Task Sync()
    {
        // Sync all calendars.
        var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);

        foreach (var account in accounts)
        {
            var t = new NewCalendarSynchronizationRequested(new CalendarSynchronizationOptions()
            {
                AccountId = account.Id,
                Type = CalendarSynchronizationType.CalendarMetadata
            }, SynchronizationSource.Client);

            Messenger.Send(t);
        }
    }

    /// <summary>
    /// When calendar type switches, we need to navigate to the most ideal date.
    /// This method returns that date.
    /// </summary>
    private DateTime GetDisplayTypeSwitchDate()
    {
        var settings = PreferencesService.GetCurrentCalendarSettings();
        switch (StatePersistenceService.CalendarDisplayType)
        {
            case CalendarDisplayType.Day:
                if (HighlightedDateRange.IsInRange(DateTime.Now)) return DateTime.Now.Date;

                return HighlightedDateRange.StartDate;
            case CalendarDisplayType.Week:
                if (HighlightedDateRange == null || HighlightedDateRange.IsInRange(DateTime.Now))
                {
                    return DateTime.Now.Date.GetWeekStartDateForDate(settings.FirstDayOfWeek);
                }

                return HighlightedDateRange.StartDate.GetWeekStartDateForDate(settings.FirstDayOfWeek);
            case CalendarDisplayType.WorkWeek:
                break;
            case CalendarDisplayType.Month:
                break;
            case CalendarDisplayType.Year:
                break;
            default:
                break;
        }

        return DateTime.Today.Date;
    }

    private DateTime? _navigationDate;
    private readonly IAccountService _accountService;
    private readonly ICalendarService _calendarService;

    #region Commands

    [RelayCommand]
    private void TodayClicked()
    {
        _navigationDate = DateTime.Now.Date;

        ForceNavigateCalendarDate();
    }

    [RelayCommand]
    public void ManageAccounts() => NavigationService.Navigate(WinoPage.AccountManagementPage);



    [RelayCommand]
    private void DateClicked(CalendarViewDayClickedEventArgs clickedDateArgs)
    {
        _navigationDate = clickedDateArgs.ClickedDate;

        ForceNavigateCalendarDate();
    }

    #endregion

    public void Receive(VisibleDateRangeChangedMessage message) => HighlightedDateRange = message.DateRange;

    /// <summary>
    /// Sets the header navigation items based on visible date range and calendar type.
    /// </summary>
    private void UpdateDateNavigationHeaderItems()
    {
        DateNavigationHeaderItems.Clear();

        // TODO: From settings
        var testInfo = new CultureInfo("en-US");

        switch (StatePersistenceService.CalendarDisplayType)
        {
            case CalendarDisplayType.Day:
            case CalendarDisplayType.Week:
            case CalendarDisplayType.WorkWeek:
            case CalendarDisplayType.Month:
                DateNavigationHeaderItems.ReplaceRange(testInfo.DateTimeFormat.MonthNames);
                break;
            case CalendarDisplayType.Year:
                break;
            default:
                break;
        }

        SetDateNavigationHeaderItems();
    }

    partial void OnHighlightedDateRangeChanged(DateRange value) => SetDateNavigationHeaderItems();

    private void SetDateNavigationHeaderItems()
    {
        if (HighlightedDateRange == null) return;

        if (DateNavigationHeaderItems.Count == 0)
        {
            UpdateDateNavigationHeaderItems();
        }

        // TODO: Year view
        var monthIndex = HighlightedDateRange.GetMostVisibleMonthIndex();

        SelectedDateNavigationHeaderIndex = Math.Max(monthIndex - 1, -1);
    }

    public async void Receive(CalendarEnableStatusChangedMessage message)
        => await ExecuteUIThread(() => IsCalendarEnabled = message.IsEnabled);

    public void Receive(NavigateManageAccountsRequested message) => SelectedMenuItemIndex = 1;

    public void Receive(CalendarDisplayTypeChangedMessage message) => OnPropertyChanged(nameof(IsVerticalCalendar));

    public async void Receive(DetailsPageStateChangedMessage message)
    {
        await ExecuteUIThread(() =>
        {
            IsEventDetailsPageActive = message.IsActivated;

            // TODO: This is for Wino Mail. Generalize this later on.
            StatePersistenceService.IsReaderNarrowed = message.IsActivated;
            StatePersistenceService.IsReadingMail = message.IsActivated;
        });
    }
}
