using System.Collections.ObjectModel;
using System.Text.RegularExpressions;
using Avalonia;
using Avalonia.Styling;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using OF_DL.Models.Config;
using Serilog;

namespace OF_DL.Gui.ViewModels;

public partial class CreatorConfigModalViewModel : ViewModelBase
{
    private static readonly Regex s_fileNameVariableRegex = new(@"\{([^{}]+)\}", RegexOptions.Compiled);

    private static readonly string[] s_postFileNameVariables = ["id", "postedAt", "mediaId", "mediaCreatedAt", "filename", "username", "text"];
    private static readonly string[] s_messageFileNameVariables = ["id", "createdAt", "mediaId", "mediaCreatedAt", "filename", "username", "text"];

    private readonly Action<bool> _onClose;
    private readonly Func<bool> _isUsernameDuplicate;
    private bool _isNormalizingUsername;
    private bool _isNormalizingFileNameFormat;

    [ObservableProperty] private bool _isOpen;
    [ObservableProperty] private bool _isEditMode;
    [ObservableProperty] private string _originalUsername = string.Empty;
    [ObservableProperty] private string _username = string.Empty;
    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(HasUsernameError))]
    private string _usernameError = string.Empty;
    [ObservableProperty] private string _paidPostFileNameFormat = string.Empty;
    [ObservableProperty] private string _postFileNameFormat = string.Empty;
    [ObservableProperty] private string _paidMessageFileNameFormat = string.Empty;
    [ObservableProperty] private string _messageFileNameFormat = string.Empty;
    [ObservableProperty] private string _selectedPaidPostVariable = string.Empty;
    [ObservableProperty] private string _selectedPostVariable = string.Empty;
    [ObservableProperty] private string _selectedPaidMessageVariable = string.Empty;
    [ObservableProperty] private string _selectedMessageVariable = string.Empty;
    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(HasUnknownPaidPostVariables))]
    private string _unknownPaidPostVariablesMessage = string.Empty;

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(HasUnknownPostVariables))]
    private string _unknownPostVariablesMessage = string.Empty;

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(HasUnknownPaidMessageVariables))]
    private string _unknownPaidMessageVariablesMessage = string.Empty;

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(HasUnknownMessageVariables))]
    private string _unknownMessageVariablesMessage = string.Empty;

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(HasPaidPostFileNameFormatError))]
    private string _paidPostFileNameFormatError = string.Empty;

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(HasPostFileNameFormatError))]
    private string _postFileNameFormatError = string.Empty;

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(HasPaidMessageFileNameFormatError))]
    private string _paidMessageFileNameFormatError = string.Empty;

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(HasMessageFileNameFormatError))]
    private string _messageFileNameFormatError = string.Empty;

    public ObservableCollection<string> AvailableUsers { get; }
    public ObservableCollection<string> PaidPostVariables { get; } = [];
    public ObservableCollection<string> PostVariables { get; } = [];
    public ObservableCollection<string> PaidMessageVariables { get; } = [];
    public ObservableCollection<string> MessageVariables { get; } = [];
    public ObservableCollection<FileNameFormatSegmentViewModel> PaidPostSegments { get; } = [];
    public ObservableCollection<FileNameFormatSegmentViewModel> PostSegments { get; } = [];
    public ObservableCollection<FileNameFormatSegmentViewModel> PaidMessageSegments { get; } = [];
    public ObservableCollection<FileNameFormatSegmentViewModel> MessageSegments { get; } = [];

    public bool HasUsernameError => !string.IsNullOrWhiteSpace(UsernameError);
    public bool HasUnknownPaidPostVariables => !string.IsNullOrWhiteSpace(UnknownPaidPostVariablesMessage);
    public bool HasUnknownPostVariables => !string.IsNullOrWhiteSpace(UnknownPostVariablesMessage);
    public bool HasUnknownPaidMessageVariables => !string.IsNullOrWhiteSpace(UnknownPaidMessageVariablesMessage);
    public bool HasUnknownMessageVariables => !string.IsNullOrWhiteSpace(UnknownMessageVariablesMessage);
    public bool HasPaidPostFileNameFormatError => !string.IsNullOrWhiteSpace(PaidPostFileNameFormatError);
    public bool HasPostFileNameFormatError => !string.IsNullOrWhiteSpace(PostFileNameFormatError);
    public bool HasPaidMessageFileNameFormatError => !string.IsNullOrWhiteSpace(PaidMessageFileNameFormatError);
    public bool HasMessageFileNameFormatError => !string.IsNullOrWhiteSpace(MessageFileNameFormatError);

    public string DialogTitle => IsEditMode ? "Edit Creator Config" : "Add Creator Config";

    public CreatorConfigModalViewModel(IEnumerable<string> availableUsers, Action<bool> onClose, Func<bool> isUsernameDuplicate)
    {
        AvailableUsers = new ObservableCollection<string>(availableUsers);
        _onClose = onClose;
        _isUsernameDuplicate = isUsernameDuplicate;

        foreach (string variable in s_postFileNameVariables)
        {
            PaidPostVariables.Add(variable);
            PostVariables.Add(variable);
        }

        foreach (string variable in s_messageFileNameVariables)
        {
            PaidMessageVariables.Add(variable);
            MessageVariables.Add(variable);
        }

        SelectedPaidPostVariable = PaidPostVariables.FirstOrDefault() ?? string.Empty;
        SelectedPostVariable = PostVariables.FirstOrDefault() ?? string.Empty;
        SelectedPaidMessageVariable = PaidMessageVariables.FirstOrDefault() ?? string.Empty;
        SelectedMessageVariable = MessageVariables.FirstOrDefault() ?? string.Empty;
    }

    public void OpenForAdd()
    {
        Log.Information("=== OpenForAdd called ===");
        IsEditMode = false;
        OriginalUsername = string.Empty;
        Username = string.Empty;
        PaidPostFileNameFormat = string.Empty;
        PostFileNameFormat = string.Empty;
        PaidMessageFileNameFormat = string.Empty;
        MessageFileNameFormat = string.Empty;
        ClearValidationErrors();
        ClearAllPreviews();
        Log.Information("About to set IsOpen = true");
        IsOpen = true;
        Log.Information("=== OpenForAdd: IsOpen is now {IsOpen} ===", IsOpen);
    }

    public void OpenForEdit(string username, CreatorConfig config)
    {
        IsEditMode = true;
        OriginalUsername = username;
        Username = username;
        PaidPostFileNameFormat = config.PaidPostFileNameFormat ?? string.Empty;
        PostFileNameFormat = config.PostFileNameFormat ?? string.Empty;
        PaidMessageFileNameFormat = config.PaidMessageFileNameFormat ?? string.Empty;
        MessageFileNameFormat = config.MessageFileNameFormat ?? string.Empty;
        ClearValidationErrors();
        UpdateAllPreviews();
        IsOpen = true;
    }

    public (string Username, CreatorConfig Config)? GetResult()
    {
        if (!Validate())
        {
            return null;
        }

        CreatorConfig config = new()
        {
            PaidPostFileNameFormat = string.IsNullOrWhiteSpace(PaidPostFileNameFormat) ? null : PaidPostFileNameFormat,
            PostFileNameFormat = string.IsNullOrWhiteSpace(PostFileNameFormat) ? null : PostFileNameFormat,
            PaidMessageFileNameFormat = string.IsNullOrWhiteSpace(PaidMessageFileNameFormat) ? null : PaidMessageFileNameFormat,
            MessageFileNameFormat = string.IsNullOrWhiteSpace(MessageFileNameFormat) ? null : MessageFileNameFormat
        };

        return (Username.Trim(), config);
    }

    [RelayCommand]
    private void InsertPaidPostVariable()
    {
        if (!string.IsNullOrWhiteSpace(SelectedPaidPostVariable))
        {
            PaidPostFileNameFormat += $"{{{SelectedPaidPostVariable}}}";
        }
    }

    [RelayCommand]
    private void InsertPostVariable()
    {
        if (!string.IsNullOrWhiteSpace(SelectedPostVariable))
        {
            PostFileNameFormat += $"{{{SelectedPostVariable}}}";
        }
    }

    [RelayCommand]
    private void InsertPaidMessageVariable()
    {
        if (!string.IsNullOrWhiteSpace(SelectedPaidMessageVariable))
        {
            PaidMessageFileNameFormat += $"{{{SelectedPaidMessageVariable}}}";
        }
    }

    [RelayCommand]
    private void InsertMessageVariable()
    {
        if (!string.IsNullOrWhiteSpace(SelectedMessageVariable))
        {
            MessageFileNameFormat += $"{{{SelectedMessageVariable}}}";
        }
    }

    [RelayCommand]
    private void Confirm()
    {
        if (Validate())
        {
            _onClose(true);
        }
    }

    [RelayCommand]
    private void Cancel()
    {
        _onClose(false);
    }

    partial void OnIsOpenChanged(bool value)
    {
        Log.Information("*** IsOpen property changed to: {Value} ***", value);
    }

    partial void OnUsernameChanged(string value)
    {
        if (_isNormalizingUsername)
        {
            return;
        }

        string trimmed = value.Trim();
        if (!string.Equals(value, trimmed, StringComparison.Ordinal))
        {
            _isNormalizingUsername = true;
            Username = trimmed;
            _isNormalizingUsername = false;
        }

        UsernameError = string.Empty;
    }

    partial void OnPaidPostFileNameFormatChanged(string value) =>
        NormalizeFileNameFormat(
            value,
            trimmed => PaidPostFileNameFormat = trimmed,
            () => PaidPostFileNameFormatError = string.Empty,
            UpdatePaidPostPreview);

    partial void OnPostFileNameFormatChanged(string value) =>
        NormalizeFileNameFormat(
            value,
            trimmed => PostFileNameFormat = trimmed,
            () => PostFileNameFormatError = string.Empty,
            UpdatePostPreview);

    partial void OnPaidMessageFileNameFormatChanged(string value) =>
        NormalizeFileNameFormat(
            value,
            trimmed => PaidMessageFileNameFormat = trimmed,
            () => PaidMessageFileNameFormatError = string.Empty,
            UpdatePaidMessagePreview);

    partial void OnMessageFileNameFormatChanged(string value) =>
        NormalizeFileNameFormat(
            value,
            trimmed => MessageFileNameFormat = trimmed,
            () => MessageFileNameFormatError = string.Empty,
            UpdateMessagePreview);

    private bool Validate()
    {
        ClearValidationErrors();
        TrimInputValues();

        bool isValid = true;

        if (string.IsNullOrWhiteSpace(Username))
        {
            UsernameError = "Username is required.";
            isValid = false;
        }

        string trimmedUsername = Username.Trim();
        if (isValid && (!IsEditMode || trimmedUsername != OriginalUsername))
        {
            if (_isUsernameDuplicate())
            {
                UsernameError = "A config for this username already exists.";
                isValid = false;
            }
        }

        ValidateFileNameFormatUniqueness(PaidPostFileNameFormat, message => PaidPostFileNameFormatError = message,
            ref isValid);
        ValidateFileNameFormatUniqueness(PostFileNameFormat, message => PostFileNameFormatError = message,
            ref isValid);
        ValidateFileNameFormatUniqueness(PaidMessageFileNameFormat, message => PaidMessageFileNameFormatError = message,
            ref isValid);
        ValidateFileNameFormatUniqueness(MessageFileNameFormat, message => MessageFileNameFormatError = message,
            ref isValid);

        return isValid;
    }

    private void ClearValidationErrors()
    {
        UsernameError = string.Empty;
        PaidPostFileNameFormatError = string.Empty;
        PostFileNameFormatError = string.Empty;
        PaidMessageFileNameFormatError = string.Empty;
        MessageFileNameFormatError = string.Empty;
    }

    private void TrimInputValues()
    {
        Username = Username.Trim();
        PaidPostFileNameFormat = PaidPostFileNameFormat.Trim();
        PostFileNameFormat = PostFileNameFormat.Trim();
        PaidMessageFileNameFormat = PaidMessageFileNameFormat.Trim();
        MessageFileNameFormat = MessageFileNameFormat.Trim();
    }

    private static void ValidateFileNameFormatUniqueness(
        string format,
        Action<string> setError,
        ref bool isValid)
    {
        if (string.IsNullOrWhiteSpace(format))
        {
            setError(string.Empty);
            return;
        }

        bool hasUniqueToken = format.Contains("{mediaId}", StringComparison.OrdinalIgnoreCase) ||
                              format.Contains("{filename}", StringComparison.OrdinalIgnoreCase);
        if (hasUniqueToken)
        {
            setError(string.Empty);
            return;
        }

        setError("Format must include {mediaId} or {filename} to avoid file collisions.");
        isValid = false;
    }

    private void NormalizeFileNameFormat(
        string value,
        Action<string> setValue,
        Action clearError,
        Action updatePreview)
    {
        if (!_isNormalizingFileNameFormat)
        {
            string trimmedValue = value.Trim();
            if (!string.Equals(value, trimmedValue, StringComparison.Ordinal))
            {
                _isNormalizingFileNameFormat = true;
                setValue(trimmedValue);
                _isNormalizingFileNameFormat = false;
                return;
            }
        }

        clearError();
        updatePreview();
    }

    private void ClearAllPreviews()
    {
        PaidPostSegments.Clear();
        PostSegments.Clear();
        PaidMessageSegments.Clear();
        MessageSegments.Clear();
        UnknownPaidPostVariablesMessage = string.Empty;
        UnknownPostVariablesMessage = string.Empty;
        UnknownPaidMessageVariablesMessage = string.Empty;
        UnknownMessageVariablesMessage = string.Empty;
    }

    private void UpdateAllPreviews()
    {
        UpdatePaidPostPreview();
        UpdatePostPreview();
        UpdatePaidMessagePreview();
        UpdateMessagePreview();
    }

    private void UpdatePaidPostPreview() =>
        UpdateFileNamePreview(PaidPostFileNameFormat, PaidPostSegments, s_postFileNameVariables, msg => UnknownPaidPostVariablesMessage = msg);

    private void UpdatePostPreview() =>
        UpdateFileNamePreview(PostFileNameFormat, PostSegments, s_postFileNameVariables, msg => UnknownPostVariablesMessage = msg);

    private void UpdatePaidMessagePreview() =>
        UpdateFileNamePreview(PaidMessageFileNameFormat, PaidMessageSegments, s_messageFileNameVariables, msg => UnknownPaidMessageVariablesMessage = msg);

    private void UpdateMessagePreview() =>
        UpdateFileNamePreview(MessageFileNameFormat, MessageSegments, s_messageFileNameVariables, msg => UnknownMessageVariablesMessage = msg);

    private void UpdateFileNamePreview(string format, ObservableCollection<FileNameFormatSegmentViewModel> segments, string[] allowedVariables, Action<string> setUnknownMessage)
    {
        segments.Clear();
        setUnknownMessage(string.Empty);

        if (string.IsNullOrEmpty(format))
        {
            return;
        }

        HashSet<string> allowedSet = new(allowedVariables, StringComparer.OrdinalIgnoreCase);
        HashSet<string> unknownVariables = new(StringComparer.OrdinalIgnoreCase);
        (string PlainTextColor, string AllowedVariableColor, string InvalidVariableColor) = GetFileNamePreviewColors();

        MatchCollection matches = s_fileNameVariableRegex.Matches(format);
        int currentIndex = 0;
        foreach (Match match in matches)
        {
            if (match.Index > currentIndex)
            {
                string plainText = format[currentIndex..match.Index];
                segments.Add(new FileNameFormatSegmentViewModel(plainText, PlainTextColor));
            }

            string variableName = match.Groups[1].Value;
            bool isAllowed = allowedSet.Contains(variableName);
            segments.Add(new FileNameFormatSegmentViewModel(match.Value,
                isAllowed ? AllowedVariableColor : InvalidVariableColor));

            if (!isAllowed)
            {
                unknownVariables.Add(variableName);
            }

            currentIndex = match.Index + match.Length;
        }

        if (currentIndex < format.Length)
        {
            string trailingText = format[currentIndex..];
            segments.Add(new FileNameFormatSegmentViewModel(trailingText, PlainTextColor));
        }

        if (unknownVariables.Count > 0)
        {
            string tokens = string.Join(", ", unknownVariables.Select(v => $"{{{v}}}"));
            setUnknownMessage($"Unknown variable(s): {tokens}");
        }
    }

    private static (string PlainTextColor, string AllowedVariableColor, string InvalidVariableColor)
        GetFileNamePreviewColors()
    {
        bool isDarkTheme = Application.Current?.RequestedThemeVariant == ThemeVariant.Dark;
        return isDarkTheme
            ? ("#DCE6F7", "#66A6FF", "#FF8C8C")
            : ("#1F2A44", "#2E6EEA", "#D84E4E");
    }
}
