using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Diagnostics;
using CommunityToolkit.Mvvm.Messaging;
using Serilog;
using Wino.Core.Domain;
using Wino.Core.Domain.Entities.Calendar;
using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Accounts;
using Wino.Core.Domain.Misc;
using Wino.Messaging.Client.Calendar;
using Wino.Messaging.Client.Accounts;
using Wino.Messaging.UI;

namespace Wino.Services;

public class AccountService : BaseDatabaseService, IAccountService
{
    public IAuthenticator ExternalAuthenticationAuthenticator { get; set; }

    private readonly ISignatureService _signatureService;
    private readonly IAuthenticationProvider _authenticationProvider;
    private readonly IMimeFileService _mimeFileService;
    private readonly IPreferencesService _preferencesService;
    private readonly IContactPictureFileService _contactPictureFileService;

    private readonly ILogger _logger = Log.ForContext<AccountService>();

    public AccountService(IDatabaseService databaseService,
                          ISignatureService signatureService,
                          IAuthenticationProvider authenticationProvider,
                          IMimeFileService mimeFileService,
                          IPreferencesService preferencesService,
                          IContactPictureFileService contactPictureFileService) : base(databaseService)
    {
        _signatureService = signatureService;
        _authenticationProvider = authenticationProvider;
        _mimeFileService = mimeFileService;
        _preferencesService = preferencesService;
        _contactPictureFileService = contactPictureFileService;
    }


    public async Task ClearAccountAttentionAsync(Guid accountId)
    {
        var account = await GetAccountAsync(accountId);

        Guard.IsNotNull(account);

        account.AttentionReason = AccountAttentionReason.None;

        await UpdateAccountAsync(account);
    }

    public async Task UpdateMergedInboxAsync(Guid mergedInboxId, IEnumerable<Guid> linkedAccountIds)
    {
        // First, remove all accounts from merged inbox.
        await Connection.ExecuteAsync("UPDATE MailAccount SET MergedInboxId = NULL WHERE MergedInboxId = ?", mergedInboxId);

        // Then, add new accounts to merged inbox.
        var accountIdList = linkedAccountIds.ToList();
        var placeholders = string.Join(",", accountIdList.Select(_ => "?"));
        var sql = $"UPDATE MailAccount SET MergedInboxId = ? WHERE Id IN ({placeholders})";
        var parameters = new List<object> { mergedInboxId };
        parameters.AddRange(accountIdList.Cast<object>());

        await Connection.ExecuteAsync(sql, parameters.ToArray());

        WeakReferenceMessenger.Default.Send(new AccountsMenuRefreshRequested());
    }

    public async Task<string> UpdateSyncIdentifierRawAsync(Guid accountId, string syncIdentifier)
    {
        await Connection.ExecuteAsync("UPDATE MailAccount SET SynchronizationDeltaIdentifier = ? WHERE Id = ?", syncIdentifier, accountId);
        return syncIdentifier;
    }

    public async Task UnlinkMergedInboxAsync(Guid mergedInboxId)
    {
        var mergedInbox = await Connection.Table<MergedInbox>().FirstOrDefaultAsync(a => a.Id == mergedInboxId).ConfigureAwait(false);

        if (mergedInbox == null)
        {
            _logger.Warning("Could not find merged inbox with id {MergedInboxId}", mergedInboxId);

            return;
        }

        await Connection.ExecuteAsync("UPDATE MailAccount SET MergedInboxId = NULL WHERE MergedInboxId = ?", mergedInboxId).ConfigureAwait(false);
        await Connection.DeleteAsync<MergedInbox>(mergedInbox.Id).ConfigureAwait(false);

        // Change the startup entity id if it was the merged inbox.
        // Take the first account as startup account.

        if (_preferencesService.StartupEntityId == mergedInboxId)
        {
            var firstAccount = await Connection.Table<MailAccount>().FirstOrDefaultAsync();

            if (firstAccount != null)
            {
                _preferencesService.StartupEntityId = firstAccount.Id;
            }
            else
            {
                _preferencesService.StartupEntityId = null;
            }
        }

        WeakReferenceMessenger.Default.Send(new AccountsMenuRefreshRequested());
    }

    public async Task CreateMergeAccountsAsync(MergedInbox mergedInbox, IEnumerable<MailAccount> accountsToMerge)
    {
        if (mergedInbox == null) return;

        // 0. Give the merged inbox a new Guid.
        mergedInbox.Id = Guid.NewGuid();

        var accountFolderDictionary = new Dictionary<MailAccount, List<MailItemFolder>>();

        // 1. Make all folders in the accounts unsticky. We will stick them based on common special folder types.
        foreach (var account in accountsToMerge)
        {
            var accountFolderList = new List<MailItemFolder>();

            var folders = await Connection.Table<MailItemFolder>().Where(a => a.MailAccountId == account.Id).ToListAsync();

            foreach (var folder in folders)
            {
                accountFolderList.Add(folder);
                folder.IsSticky = false;

                await Connection.UpdateAsync(folder, typeof(MailItemFolder));
            }

            accountFolderDictionary.Add(account, accountFolderList);
        }

        // 2. Find the common special folders and stick them.
        // Only following types will be considered as common special folder.
        SpecialFolderType[] commonSpecialTypes =
        [
            SpecialFolderType.Inbox,
            SpecialFolderType.Sent,
            SpecialFolderType.Draft,
            SpecialFolderType.Archive,
            SpecialFolderType.Junk,
            SpecialFolderType.Deleted
        ];

        foreach (var type in commonSpecialTypes)
        {
            var isCommonType = accountFolderDictionary
                .Select(a => a.Value)
                .Where(a => a.Any(a => a.SpecialFolderType == type))
                .Count() == accountsToMerge.Count();

            if (isCommonType)
            {
                foreach (var account in accountsToMerge)
                {
                    var folder = accountFolderDictionary[account].FirstOrDefault(a => a.SpecialFolderType == type);

                    if (folder != null)
                    {
                        folder.IsSticky = true;

                        await Connection.UpdateAsync(folder, typeof(MailItemFolder));
                    }
                }
            }
        }

        // 3. Insert merged inbox and assign accounts.
        await Connection.InsertAsync(mergedInbox, typeof(MergedInbox));

        foreach (var account in accountsToMerge)
        {
            account.MergedInboxId = mergedInbox.Id;

            await Connection.UpdateAsync(account, typeof(MailAccount));
        }

        WeakReferenceMessenger.Default.Send(new AccountsMenuRefreshRequested());
    }

    public async Task RenameMergedAccountAsync(Guid mergedInboxId, string newName)
    {
        await Connection.ExecuteAsync("UPDATE MergedInbox SET Name = ? WHERE Id = ?", newName, mergedInboxId);

        ReportUIChange(new MergedInboxRenamed(mergedInboxId, newName));
    }

    public async Task FixTokenIssuesAsync(Guid accountId)
    {
        var account = await Connection.Table<MailAccount>().FirstOrDefaultAsync(a => a.Id == accountId);

        if (account == null) return;

        var authenticator = _authenticationProvider.GetAuthenticator(account.ProviderType);

        // This will re-generate token with interactive authentication
        // New authentication will include calendar scopes
        var token = await authenticator.GenerateTokenInformationAsync(account);

        Guard.IsNotNull(token);

        // Enable calendar access since new token includes calendar scopes
        account.IsCalendarAccessGranted = true;

        await UpdateAccountAsync(account);
    }

    private Task<MailAccountPreferences> GetAccountPreferencesAsync(Guid accountId)
        => Connection.Table<MailAccountPreferences>().FirstOrDefaultAsync(a => a.AccountId == accountId);

    public async Task<List<MailAccount>> GetAccountsAsync()
    {
        var accounts = await Connection.Table<MailAccount>().OrderBy(a => a.Order).ToListAsync();

        foreach (var account in accounts)
        {
            // Load IMAP server configuration.
            if (account.ProviderType == MailProviderType.IMAP4)
                account.ServerInformation = await GetAccountCustomServerInformationAsync(account.Id);

            // Load MergedInbox information.
            if (account.MergedInboxId != null)
                account.MergedInbox = await GetMergedInboxInformationAsync(account.MergedInboxId.Value);

            account.Preferences = await GetAccountPreferencesAsync(account.Id);
        }

        return accounts;
    }

    public async Task CreateRootAliasAsync(Guid accountId, string address)
    {
        var rootAlias = new MailAccountAlias()
        {
            AccountId = accountId,
            AliasAddress = address,
            IsPrimary = true,
            IsRootAlias = true,
            IsVerified = true,
            ReplyToAddress = address,
            Id = Guid.NewGuid()
        };

        await Connection.InsertAsync(rootAlias, typeof(MailAccountAlias)).ConfigureAwait(false);

        Log.Information("Created root alias for the account {AccountId}", accountId);
    }

    public async Task<List<MailAccountAlias>> GetAccountAliasesAsync(Guid accountId)
    {
        return await Connection.QueryAsync<MailAccountAlias>(
            "SELECT * FROM MailAccountAlias WHERE AccountId = ? ORDER BY IsRootAlias DESC",
            accountId).ConfigureAwait(false);
    }

    private Task<MergedInbox> GetMergedInboxInformationAsync(Guid mergedInboxId)
        => Connection.Table<MergedInbox>().FirstOrDefaultAsync(a => a.Id == mergedInboxId);

    public async Task DeleteAccountMailCacheAsync(Guid accountId, AccountCacheResetReason accountCacheResetReason)
    {
        await Connection.ExecuteAsync(
            "DELETE FROM MailCopy WHERE Id IN (SELECT Id FROM MailCopy WHERE FolderId IN (SELECT Id FROM MailItemFolder WHERE MailAccountId = ?))",
            accountId);

        WeakReferenceMessenger.Default.Send(new AccountCacheResetMessage(accountId, accountCacheResetReason));
    }

    public async Task DeleteAccountAsync(MailAccount account)
    {
        // Collect calendar entities before deletion so we can notify UI subscribers.
        var accountCalendars = await Connection.Table<AccountCalendar>()
            .Where(a => a.AccountId == account.Id)
            .ToListAsync()
            .ConfigureAwait(false);

        var deletedCalendarItems = new List<CalendarItem>();
        foreach (var accountCalendar in accountCalendars)
        {
            var calendarItems = await Connection.Table<CalendarItem>()
                .Where(a => a.CalendarId == accountCalendar.Id)
                .ToListAsync()
                .ConfigureAwait(false);

            deletedCalendarItems.AddRange(calendarItems);
        }

        await DeleteAccountMailCacheAsync(account.Id, AccountCacheResetReason.AccountRemoval);

        // Delete calendar metadata and related records for this account.
        foreach (var calendarItem in deletedCalendarItems)
        {
            await Connection.Table<CalendarEventAttendee>().DeleteAsync(a => a.CalendarItemId == calendarItem.Id).ConfigureAwait(false);
            await Connection.Table<Reminder>().DeleteAsync(a => a.CalendarItemId == calendarItem.Id).ConfigureAwait(false);
            await Connection.Table<CalendarAttachment>().DeleteAsync(a => a.CalendarItemId == calendarItem.Id).ConfigureAwait(false);
        }

        foreach (var accountCalendar in accountCalendars)
        {
            await Connection.Table<CalendarItem>().DeleteAsync(a => a.CalendarId == accountCalendar.Id).ConfigureAwait(false);
        }

        await Connection.Table<AccountCalendar>().DeleteAsync(a => a.AccountId == account.Id).ConfigureAwait(false);

        await Connection.Table<MailItemFolder>().DeleteAsync(a => a.MailAccountId == account.Id);
        await Connection.Table<AccountSignature>().DeleteAsync(a => a.MailAccountId == account.Id);
        await Connection.Table<MailAccountAlias>().DeleteAsync(a => a.AccountId == account.Id);

        // Account belongs to a merged inbox.
        // In case of there'll be a single account in the merged inbox, remove the merged inbox as well.

        if (account.MergedInboxId != null)
        {
            var mergedInboxAccountCount = await Connection.Table<MailAccount>().Where(a => a.MergedInboxId == account.MergedInboxId.Value).CountAsync();

            // There will be only one account in the merged inbox. Remove the link for the other account as well.
            if (mergedInboxAccountCount == 2)
            {
                await Connection.ExecuteAsync(
                    "UPDATE MailAccount SET MergedInboxId = NULL WHERE MergedInboxId = ?",
                    account.MergedInboxId.Value).ConfigureAwait(false);
            }
        }

        if (account.ProviderType == MailProviderType.IMAP4)
            await Connection.Table<CustomServerInformation>().DeleteAsync(a => a.AccountId == account.Id);

        if (account.Preferences != null)
            await Connection.DeleteAsync<MailAccountPreferences>(account.Preferences.Id);

        await Connection.DeleteAsync<MailAccount>(account.Id);

        await _mimeFileService.DeleteUserMimeCacheAsync(account.Id).ConfigureAwait(false);

        // Clear out or set up a new startup entity id.
        // Next account after the deleted one will be the startup account.

        if (_preferencesService.StartupEntityId == account.Id || _preferencesService.StartupEntityId == account.MergedInboxId)
        {
            var firstNonStartupAccount = await Connection.Table<MailAccount>().FirstOrDefaultAsync(a => a.Id != account.Id);

            if (firstNonStartupAccount != null)
            {
                _preferencesService.StartupEntityId = firstNonStartupAccount.Id;
            }
            else
            {
                _preferencesService.StartupEntityId = null;
            }
        }

        foreach (var calendarItem in deletedCalendarItems)
        {
            WeakReferenceMessenger.Default.Send(new CalendarItemDeleted(calendarItem, EntityUpdateSource.Server));
        }

        foreach (var accountCalendar in accountCalendars)
        {
            WeakReferenceMessenger.Default.Send(new CalendarListDeleted(accountCalendar));
        }

        ReportUIChange(new AccountRemovedMessage(account));
    }

    public async Task UpdateProfileInformationAsync(Guid accountId, ProfileInformation profileInformation)
    {
        var account = await GetAccountAsync(accountId).ConfigureAwait(false);

        if (account != null)
        {
            account.SenderName = profileInformation.SenderName;
            account.Base64ProfilePictureData = profileInformation.Base64ProfilePictureData;

            if (string.IsNullOrEmpty(account.Address))
            {
                account.Address = profileInformation.AccountAddress;
            }
            // Forcefully add or update a contact data with the provided information.

            var existingContact = await Connection.Table<AccountContact>()
                .FirstOrDefaultAsync(a => a.Address == account.Address)
                .ConfigureAwait(false);

            var contactPictureFileId = await SaveProfilePictureAsync(
                account.Base64ProfilePictureData,
                existingContact?.ContactPictureFileId).ConfigureAwait(false);

            var accountContact = new AccountContact()
            {
                Address = account.Address,
                Name = account.SenderName,
                ContactPictureFileId = contactPictureFileId,
                IsRootContact = true
            };

            await Connection.InsertOrReplaceAsync(accountContact, typeof(AccountContact)).ConfigureAwait(false);

            await UpdateAccountAsync(account).ConfigureAwait(false);
        }
    }

    private async Task<Guid?> SaveProfilePictureAsync(string base64ProfilePictureData, Guid? existingFileId)
    {
        if (string.IsNullOrWhiteSpace(base64ProfilePictureData))
        {
            if (existingFileId.HasValue)
                await _contactPictureFileService.DeleteContactPictureAsync(existingFileId.Value).ConfigureAwait(false);

            return null;
        }

        byte[] bytes;
        try
        {
            bytes = Convert.FromBase64String(base64ProfilePictureData);
        }
        catch (FormatException ex)
        {
            _logger.Warning(ex, "Failed to decode account profile picture for contact migration.");
            return existingFileId;
        }

        var newFileId = await _contactPictureFileService.SaveContactPictureAsync(bytes).ConfigureAwait(false);

        if (existingFileId.HasValue)
            await _contactPictureFileService.DeleteContactPictureAsync(existingFileId.Value).ConfigureAwait(false);

        return newFileId;
    }

    public async Task<MailAccount> GetAccountAsync(Guid accountId)
    {
        var account = await Connection.Table<MailAccount>().FirstOrDefaultAsync(a => a.Id == accountId);

        if (account == null)
        {
            _logger.Error("Could not find account with id {AccountId}", accountId);
        }
        else
        {
            if (account.ProviderType == MailProviderType.IMAP4)
                account.ServerInformation = await GetAccountCustomServerInformationAsync(account.Id);

            account.Preferences = await GetAccountPreferencesAsync(account.Id);

            return account;
        }

        return null;
    }

    public Task<CustomServerInformation> GetAccountCustomServerInformationAsync(Guid accountId)
        => Connection.Table<CustomServerInformation>().FirstOrDefaultAsync(a => a.AccountId == accountId);

    public async Task UpdateAccountAsync(MailAccount account)
    {
        await Connection.UpdateAsync(account.Preferences, typeof(MailAccountPreferences)).ConfigureAwait(false);
        await Connection.UpdateAsync(account, typeof(MailAccount)).ConfigureAwait(false);

        ReportUIChange(new AccountUpdatedMessage(account));
    }

    public async Task UpdateAccountCustomServerInformationAsync(CustomServerInformation customServerInformation)
    {
        await Connection.UpdateAsync(customServerInformation, typeof(CustomServerInformation)).ConfigureAwait(false);
    }

    public async Task UpdateAccountAliasesAsync(Guid accountId, List<MailAccountAlias> aliases)
    {
        // Delete existing ones.
        await Connection.Table<MailAccountAlias>().DeleteAsync(a => a.AccountId == accountId).ConfigureAwait(false);

        // Insert new ones.
        foreach (var alias in aliases)
        {
            await Connection.InsertAsync(alias, typeof(MailAccountAlias)).ConfigureAwait(false);
        }
    }

    public async Task UpdateRemoteAliasInformationAsync(MailAccount account, List<RemoteAccountAlias> remoteAccountAliases)
    {
        var localAliases = await GetAccountAliasesAsync(account.Id).ConfigureAwait(false);
        var rootAlias = localAliases.Find(a => a.IsRootAlias);

        foreach (var remoteAlias in remoteAccountAliases)
        {
            var existingAlias = localAliases.Find(a => a.AccountId == account.Id && a.AliasAddress == remoteAlias.AliasAddress);

            if (existingAlias == null)
            {
                // Create new alias.
                var newAlias = new MailAccountAlias()
                {
                    AccountId = account.Id,
                    AliasAddress = remoteAlias.AliasAddress,
                    IsPrimary = remoteAlias.IsPrimary,
                    IsVerified = remoteAlias.IsVerified,
                    ReplyToAddress = remoteAlias.ReplyToAddress,
                    Id = Guid.NewGuid(),
                    IsRootAlias = remoteAlias.IsRootAlias,
                    AliasSenderName = remoteAlias.AliasSenderName
                };

                await Connection.InsertAsync(newAlias, typeof(MailAccountAlias));
                localAliases.Add(newAlias);
            }
            else
            {
                // Update existing alias.
                existingAlias.IsPrimary = remoteAlias.IsPrimary;
                existingAlias.IsVerified = remoteAlias.IsVerified;
                existingAlias.ReplyToAddress = remoteAlias.ReplyToAddress;
                existingAlias.AliasSenderName = remoteAlias.AliasSenderName;

                await Connection.UpdateAsync(existingAlias, typeof(MailAccountAlias));
            }
        }

        // Make sure there is only 1 root alias and 1 primary alias selected.

        bool shouldUpdatePrimary = localAliases.Count(a => a.IsPrimary) != 1;
        bool shouldUpdateRoot = localAliases.Count(a => a.IsRootAlias) != 1;

        if (shouldUpdatePrimary)
        {
            localAliases.ForEach(a => a.IsPrimary = false);

            var idealPrimaryAlias = localAliases.Find(a => a.AliasAddress == account.Address) ?? localAliases.First();

            idealPrimaryAlias.IsPrimary = true;
            await Connection.UpdateAsync(idealPrimaryAlias, typeof(MailAccountAlias)).ConfigureAwait(false);
        }

        if (shouldUpdateRoot)
        {
            localAliases.ForEach(a => a.IsRootAlias = false);

            var idealRootAlias = localAliases.Find(a => a.AliasAddress == account.Address) ?? localAliases.First();

            idealRootAlias.IsRootAlias = true;
            await Connection.UpdateAsync(idealRootAlias, typeof(MailAccountAlias)).ConfigureAwait(false);
        }
    }

    public async Task DeleteAccountAliasAsync(Guid aliasId)
    {
        // Create query to delete alias.

        await Connection.ExecuteAsync("DELETE FROM MailAccountAlias WHERE Id = ?", aliasId).ConfigureAwait(false);
    }

    public async Task CreateAccountAsync(MailAccount account, CustomServerInformation customServerInformation)
    {
        Guard.IsNotNull(account);

        var accountCount = await Connection.Table<MailAccount>().CountAsync();

        // If there are no accounts before this one, set it as startup account.
        if (accountCount == 0)
        {
            _preferencesService.StartupEntityId = account.Id;
        }
        else
        {
            // Set the order of the account.
            // This can be changed by the user later in manage accounts page.
            account.Order = accountCount;
        }

        await Connection.InsertAsync(account, typeof(MailAccount));

        var preferences = new MailAccountPreferences()
        {
            Id = Guid.NewGuid(),
            AccountId = account.Id,
            IsNotificationsEnabled = true,
            ShouldAppendMessagesToSentFolder = false
        };

        // iCloud does not appends sent messages to sent folder automatically.
        if (account.SpecialImapProvider == SpecialImapProvider.iCloud || account.SpecialImapProvider == SpecialImapProvider.Yahoo)
        {
            preferences.ShouldAppendMessagesToSentFolder = true;
        }

        account.Preferences = preferences;

        // Outlook & Office 365 supports Focused inbox. Enabled by default.
        bool isMicrosoftProvider = account.ProviderType == MailProviderType.Outlook;

        // TODO: This should come from account settings API.
        // Wino doesn't have MailboxSettings yet.
        if (isMicrosoftProvider)
            account.Preferences.IsFocusedInboxEnabled = true;

        // Setup default signature.
        var defaultSignature = await _signatureService.CreateDefaultSignatureAsync(account.Id);

        account.Preferences.SignatureIdForNewMessages = defaultSignature.Id;
        account.Preferences.SignatureIdForFollowingMessages = defaultSignature.Id;
        account.Preferences.IsSignatureEnabled = true;

        await Connection.InsertAsync(preferences, typeof(MailAccountPreferences));

        if (customServerInformation != null)
            await Connection.InsertAsync(customServerInformation, typeof(CustomServerInformation));

        if (account.ProviderType == MailProviderType.IMAP4 &&
            customServerInformation?.CalendarSupportMode == ImapCalendarSupportMode.LocalOnly)
        {
            await EnsureDefaultLocalCalendarForImapAsync(account.Id).ConfigureAwait(false);
        }
    }

    private async Task EnsureDefaultLocalCalendarForImapAsync(Guid accountId)
    {
        var existingCalendarCount = await Connection.Table<AccountCalendar>()
            .Where(a => a.AccountId == accountId)
            .CountAsync()
            .ConfigureAwait(false);

        if (existingCalendarCount > 0)
            return;

        var localCalendar = new AccountCalendar
        {
            Id = Guid.NewGuid(),
            AccountId = accountId,
            Name = Translator.AccountDetailsPage_TabCalendar,
            IsPrimary = true,
            IsSynchronizationEnabled = true,
            IsExtended = true,
            RemoteCalendarId = string.Empty,
            TimeZone = string.Empty,
            BackgroundColorHex = await GetNextDistinctCalendarColorAsync().ConfigureAwait(false)
        };

        localCalendar.TextColorHex = GetReadableTextColorHex(localCalendar.BackgroundColorHex);

        await Connection.InsertAsync(localCalendar, typeof(AccountCalendar)).ConfigureAwait(false);
    }

    private async Task<string> GetNextDistinctCalendarColorAsync()
    {
        var usedColors = await Connection.Table<AccountCalendar>()
            .ToListAsync()
            .ConfigureAwait(false);

        return CalendarColorPalette.GetDistinctColor(usedColors.Select(a => a.BackgroundColorHex));
    }

    private static string GetReadableTextColorHex(string backgroundColorHex)
    {
        if (!TryParseHexColor(backgroundColorHex, out var red, out var green, out var blue))
            return "#FFFFFF";

        var luminance = ((0.299 * red) + (0.587 * green) + (0.114 * blue)) / 255d;
        return luminance > 0.6 ? "#111111" : "#FFFFFF";
    }

    private static bool TryParseHexColor(string value, out int red, out int green, out int blue)
    {
        red = 255;
        green = 255;
        blue = 255;

        if (string.IsNullOrWhiteSpace(value))
            return false;

        var color = value.Trim();
        if (color.StartsWith('#'))
        {
            color = color[1..];
        }

        if (color.Length != 6 ||
            !int.TryParse(color, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out _))
        {
            return false;
        }

        red = Convert.ToInt32(color.Substring(0, 2), 16);
        green = Convert.ToInt32(color.Substring(2, 2), 16);
        blue = Convert.ToInt32(color.Substring(4, 2), 16);
        return true;
    }

    public async Task UpdateAccountOrdersAsync(Dictionary<Guid, int> accountIdOrderPair)
    {
        foreach (var pair in accountIdOrderPair)
        {
            var account = await GetAccountAsync(pair.Key);

            if (account == null)
            {
                _logger.Information("Could not find account with id {Key} for reordering. It may be a linked account.", pair.Key);
                continue;
            }

            account.Order = pair.Value;

            await Connection.UpdateAsync(account, typeof(MailAccount));
        }

        Messenger.Send(new AccountMenuItemsReordered(accountIdOrderPair));
    }

    public async Task<MailAccountAlias> GetPrimaryAccountAliasAsync(Guid accountId)
    {
        var aliases = await GetAccountAliasesAsync(accountId);

        if (aliases == null || aliases.Count == 0) return null;

        return aliases.FirstOrDefault(a => a.IsPrimary) ?? aliases.First();
    }

    public async Task<bool> IsAccountFocusedEnabledAsync(Guid accountId)
    {
        var account = await GetAccountAsync(accountId);
        return account.Preferences.IsFocusedInboxEnabled.GetValueOrDefault();
    }

    public async Task<bool> IsNotificationsEnabled(Guid accountId)
    {
        var account = await GetAccountAsync(accountId);

        return account?.Preferences?.IsNotificationsEnabled ?? false;
    }

    public async Task UpdateLastFolderStructureSyncDateAsync(Guid accountId)
    {
        var account = await GetAccountAsync(accountId);
        if (account == null) return;

        account.LastFolderStructureSyncDate = DateTime.UtcNow;
        await Connection.UpdateAsync(account, typeof(MailAccount)).ConfigureAwait(false);
    }

    public async Task<bool> ShouldSyncFolderStructureAsync(Guid accountId, TimeSpan syncInterval)
    {
        var account = await GetAccountAsync(accountId);
        if (account == null) return true;

        if (!account.LastFolderStructureSyncDate.HasValue)
            return true;

        return DateTime.UtcNow - account.LastFolderStructureSyncDate.Value > syncInterval;
    }
}
