using Nickvision.Desktop.Globalization;
using Nickvision.Desktop.GNOME.Helpers;
using Nickvision.Parabolic.GNOME.Helpers;
using Nickvision.Parabolic.Shared.Controllers;
using Nickvision.Parabolic.Shared.Models;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;

namespace Nickvision.Parabolic.GNOME.Views;

public class PreferencesDialog : Adw.PreferencesDialog
{
    private readonly PreferencesViewController _controller;
    private readonly ITranslationService _translationService;
    private readonly Gtk.Window _parent;
    private readonly Gtk.Builder _builder;
    private readonly List<Adw.ActionRow> _postprocessingArgumentRows;
    private EditMode _postprocessingArgumentEditMode;

    [Gtk.Connect("themeRow")]
    private Adw.ComboRow? _themeRow;
    [Gtk.Connect("languageRow")]
    private Adw.ComboRow? _languageRow;
    [Gtk.Connect("previewUpdatesRow")]
    private Adw.SwitchRow? _previewUpdatesRow;
    [Gtk.Connect("preventSuspendRow")]
    private Adw.SwitchRow? _preventSuspendRow;
    [Gtk.Connect("historyLengthRow")]
    private Adw.ComboRow? _historyLengthRow;
    [Gtk.Connect("maxNumberOfActiveDownloadsRow")]
    private Adw.SpinRow? _maxNumberOfActiveDownloadsRow;
    [Gtk.Connect("overwriteExistingFilesRow")]
    private Adw.SwitchRow _overwriteExistingFilesRow;
    [Gtk.Connect("limitCharactersRow")]
    private Adw.SwitchRow _limitCharactersRow;
    [Gtk.Connect("includeMediaIdRow")]
    private Adw.SwitchRow _includeMediaIdRow;
    [Gtk.Connect("includeAutoGeneratedSubtitlesRow")]
    private Adw.SwitchRow _includeAutoGeneratedSubtitlesRow;
    [Gtk.Connect("includeSuperResolutionsRow")]
    private Adw.SwitchRow _includeSuperResolutionsRow;
    [Gtk.Connect("preferredVideoCodecRow")]
    private Adw.ComboRow? _preferredVideoCodecRow;
    [Gtk.Connect("preferredAudioCodecRow")]
    private Adw.ComboRow? _preferredAudioCodecRow;
    [Gtk.Connect("preferredSubtitleFormatRow")]
    private Adw.ComboRow? _preferredSubtitleFormatRow;
    [Gtk.Connect("preferredFrameRateRow")]
    private Adw.ComboRow? _preferredFrameRateRow;
    [Gtk.Connect("usePartFilesRow")]
    private Adw.SwitchRow _usePartFilesRow;
    [Gtk.Connect("sponsorBlockRow")]
    private Adw.SwitchRow _sponsorBlockRow;
    [Gtk.Connect("limitSpeedRow")]
    private Adw.ExpanderRow? _limitSpeedRow;
    [Gtk.Connect("speedLimitRow")]
    private Adw.SpinRow? _speedLimitRow;
    [Gtk.Connect("proxyUrlRow")]
    private Adw.EntryRow? _proxyUrlRow;
    [Gtk.Connect("cookiesFileRow")]
    private Adw.ActionRow? _cookiesFileRow;
    [Gtk.Connect("selectCookiesFileButton")]
    private Gtk.Button? _selectCookiesFileButton;
    [Gtk.Connect("clearCookiesFileButton")]
    private Gtk.Button? _clearCookiesFileButton;
    [Gtk.Connect("cookiesBrowserRow")]
    private Adw.ComboRow? _cookiesBrowserRow;
    [Gtk.Connect("useAriaRow")]
    private Adw.SwitchRow _useAriaRow;
    [Gtk.Connect("ariaMaxConnectionsPerServerRow")]
    private Adw.SpinRow? _ariaMaxConnectionsPerServerRow;
    [Gtk.Connect("ariaMinSplitSizeRow")]
    private Adw.SpinRow? _ariaMinSplitSizeRow;
    [Gtk.Connect("ytdlpDiscoveryArgsRow")]
    private Adw.EntryRow? _ytdlpDiscoveryArgsRow;
    [Gtk.Connect("ytdlpDownloadArgsRow")]
    private Adw.EntryRow? _ytdlpDownloadArgsRow;
    [Gtk.Connect("embedMetadataRow")]
    private Adw.ExpanderRow? _embedMetadataRow;
    [Gtk.Connect("removeSourceDataRow")]
    private Adw.SwitchRow _removeSourceDataRow;
    [Gtk.Connect("translateMetadataChaptersRow")]
    private Adw.SwitchRow _translateMetadataChaptersRow;
    [Gtk.Connect("embedThumbnailsRow")]
    private Adw.ExpanderRow? _embedThumbnailsRow;
    [Gtk.Connect("cropAudioThumbnailsRow")]
    private Adw.SwitchRow _cropAudioThumbnailsRow;
    [Gtk.Connect("embedChaptersRow")]
    private Adw.SwitchRow _embedChaptersRow;
    [Gtk.Connect("embedSubtitlesRow")]
    private Adw.SwitchRow _embedSubtitlesRow;
    [Gtk.Connect("postprocessingThreadsRow")]
    private Adw.SpinRow? _postprocessingThreadsRow;
    [Gtk.Connect("postprocessingArgumentsGroup")]
    private Adw.PreferencesGroup? _postprocessingArgumentsGroup;
    [Gtk.Connect("addPostprocessingArgumentButton")]
    private Gtk.Button? _addPostprocessingArgumentButton;
    [Gtk.Connect("editPostprocessingArgumentDialog")]
    private Adw.Dialog? _editPostprocessingArgumentDialog;
    [Gtk.Connect("editArgumentNameRow")]
    private Adw.EntryRow? _editArgumentNameRow;
    [Gtk.Connect("editArgumentPostProcessorRow")]
    private Adw.ComboRow? _editArgumentPostProcessorRow;
    [Gtk.Connect("editArgumentExecutableRow")]
    private Adw.ComboRow? _editArgumentExecutableRow;
    [Gtk.Connect("editArgumentArgsRow")]
    private Adw.EntryRow? _editArgumentArgsRow;
    [Gtk.Connect("editConfirmPostprocessingArgumentButton")]
    private Gtk.Button? _editConfirmPostprocessingArgumentButton;

    public PreferencesDialog(PreferencesViewController controller, ITranslationService translationService, Adw.ApplicationWindow parent, IGtkBuilderFactory builderFactory) : this(controller, translationService, parent, builderFactory.Create("PreferencesDialog"))
    {

    }

    [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicFields, typeof(PreferencesDialog))]
    private PreferencesDialog(PreferencesViewController controller, ITranslationService translationService, Gtk.Window parent, Gtk.Builder builder) : base(new Adw.Internal.PreferencesDialogHandle(builder.GetPointer("root"), false))
    {
        _controller = controller;
        _translationService = translationService;
        _parent = parent;
        _builder = builder;
        _postprocessingArgumentRows = new List<Adw.ActionRow>();
        _postprocessingArgumentEditMode = EditMode.None;
        _builder.Connect(this);
        // Load
        _themeRow!.SetModel(_controller.Themes);
        _languageRow!.SetModel(_controller.AvailableTranslationLanguages);
        _previewUpdatesRow!.Active = _controller.AllowPreviewUpdates;
        _preventSuspendRow!.Active = _controller.PreventSuspend;
        _historyLengthRow!.SetModel(_controller.HistoryLengths);
        _maxNumberOfActiveDownloadsRow!.Value = _controller.MaxNumberOfActiveDownloads;
        _overwriteExistingFilesRow!.Active = _controller.OverwriteExistingFiles;
        _limitCharactersRow!.Active = _controller.LimitCharacters;
        _includeMediaIdRow!.Active = _controller.IncludeMediaIdInTitle;
        _includeAutoGeneratedSubtitlesRow!.Active = _controller.IncludeAutoGeneratedSubtitles;
        _includeSuperResolutionsRow!.Active = _controller.IncludeSuperResolutions;
        _preferredVideoCodecRow!.SetModel(_controller.VideoCodecs);
        _preferredAudioCodecRow!.SetModel(_controller.AudioCodecs);
        _preferredSubtitleFormatRow!.SetModel(_controller.SubtitleFormats);
        _preferredFrameRateRow!.SetModel(_controller.FrameRates);
        _usePartFilesRow!.Active = _controller.UsePartFiles;
        _sponsorBlockRow!.Active = _controller.YouTubeSponsorBlock;
        _limitSpeedRow!.EnableExpansion = _controller.SpeedLimit.HasValue;
        _speedLimitRow!.Value = _controller.SpeedLimit ?? _speedLimitRow!.Value;
        _proxyUrlRow!.Text_ = _controller.ProxyUrl;
        _cookiesFileRow!.Subtitle = !File.Exists(_controller.CookiesPath) ? _translationService._("No file selected") : _controller.CookiesPath;
        _cookiesBrowserRow!.SetModel(_controller.Browsers);
        _useAriaRow!.Active = _controller.UseAria;
        _ariaMaxConnectionsPerServerRow!.Value = _controller.AriaMaxConnectionsPerServer;
        _ariaMinSplitSizeRow!.Value = _controller.AriaMinSplitSize;
        _ytdlpDiscoveryArgsRow!.Text_ = _controller.YtdlpDiscoveryArgs;
        _ytdlpDownloadArgsRow!.Text_ = _controller.YtdlpDownloadArgs;
        _translateMetadataChaptersRow!.Active = _controller.TranslateMetadataAndChapters;
        _embedMetadataRow!.EnableExpansion = _controller.EmbedMetadata;
        _removeSourceDataRow!.Active = _controller.RemoveSourceData;
        _embedThumbnailsRow!.EnableExpansion = _controller.EmbedThumbnails;
        _cropAudioThumbnailsRow!.Active = _controller.CropAudioThumbnails;
        _embedChaptersRow!.Active = _controller.EmbedChapters;
        _embedSubtitlesRow!.Active = _controller.EmbedSubtitles;
        _postprocessingThreadsRow!.Adjustment = Gtk.Adjustment.New(_controller.PostprocessingThreads, 1, Environment.ProcessorCount, 1, 1, 1);
        PostprocessingArugments_CollectionChanged(null, null);
        // Events
        OnClosed += Dialog_OnClosed;
        _controller.PostprocessingArguments.CollectionChanged += PostprocessingArugments_CollectionChanged;
        _themeRow!.OnNotify += ThemeRow_OnNotify;
        _selectCookiesFileButton!.OnClicked += SelectCookiesFileButton_OnClicked;
        _clearCookiesFileButton!.OnClicked += ClearCookiesFileButton_OnClicked;
        _addPostprocessingArgumentButton!.OnClicked += AddPostprocessingArgumentButton_OnClicked;
        _editConfirmPostprocessingArgumentButton!.OnClicked += EditConfirmPostprocessingArgumentButton_OnClicked;
    }

    private async void Dialog_OnClosed(Adw.Dialog sender, EventArgs args)
    {
        _controller.TranslationLanguage = _controller.AvailableTranslationLanguages[(int)_languageRow!.Selected];
        _controller.AllowPreviewUpdates = _previewUpdatesRow!.Active;
        _controller.PreventSuspend = _preventSuspendRow!.Active;
        _controller.HistoryLength = _controller.HistoryLengths[(int)_historyLengthRow!.Selected];
        _controller.MaxNumberOfActiveDownloads = (int)_maxNumberOfActiveDownloadsRow!.Value;
        _controller.OverwriteExistingFiles = _overwriteExistingFilesRow!.Active;
        _controller.LimitCharacters = _limitCharactersRow!.Active;
        _controller.IncludeMediaIdInTitle = _includeMediaIdRow!.Active;
        _controller.IncludeAutoGeneratedSubtitles = _includeAutoGeneratedSubtitlesRow!.Active;
        _controller.IncludeSuperResolutions = _includeSuperResolutionsRow!.Active;
        _controller.PreferredVideoCodec = _controller.VideoCodecs[(int)_preferredVideoCodecRow!.Selected];
        _controller.PreferredAudioCodec = _controller.AudioCodecs[(int)_preferredAudioCodecRow!.Selected];
        _controller.PreferredSubtitleFormat = _controller.SubtitleFormats[(int)_preferredSubtitleFormatRow!.Selected];
        _controller.PreferredFrameRate = _controller.FrameRates[(int)_preferredFrameRateRow!.Selected];
        _controller.UsePartFiles = _usePartFilesRow!.Active;
        _controller.YouTubeSponsorBlock = _sponsorBlockRow!.Active;
        _controller.SpeedLimit = _limitSpeedRow!.EnableExpansion ? (int)_speedLimitRow!.Value : null;
        _controller.ProxyUrl = _proxyUrlRow!.Text_ ?? string.Empty;
        _controller.CookiesPath = File.Exists(_cookiesFileRow!.Subtitle) ? _cookiesFileRow!.Subtitle : string.Empty;
        _controller.CookiesBrowser = _controller.Browsers[(int)_cookiesBrowserRow!.Selected];
        _controller.UseAria = _useAriaRow!.Active;
        _controller.AriaMaxConnectionsPerServer = (int)_ariaMaxConnectionsPerServerRow!.Value;
        _controller.AriaMinSplitSize = (int)_ariaMinSplitSizeRow!.Value;
        _controller.YtdlpDiscoveryArgs = _ytdlpDiscoveryArgsRow!.Text_ ?? string.Empty;
        _controller.YtdlpDownloadArgs = _ytdlpDownloadArgsRow!.Text_ ?? string.Empty;
        _controller.TranslateMetadataAndChapters = _translateMetadataChaptersRow!.Active;
        _controller.EmbedMetadata = _embedMetadataRow!.EnableExpansion;
        _controller.RemoveSourceData = _removeSourceDataRow!.Active;
        _controller.EmbedThumbnails = _embedThumbnailsRow!.EnableExpansion;
        _controller.CropAudioThumbnails = _cropAudioThumbnailsRow!.Active;
        _controller.EmbedChapters = _embedChaptersRow!.Active;
        _controller.EmbedSubtitles = _embedSubtitlesRow!.Active;
        _controller.PostprocessingThreads = (int)_postprocessingThreadsRow!.Value;
        await _controller.SaveConfigurationAsync();
    }

    private void PostprocessingArugments_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
    {
        foreach (var row in _postprocessingArgumentRows)
        {
            _postprocessingArgumentsGroup!.Remove(row);
        }
        _postprocessingArgumentRows.Clear();
        foreach (var argument in _controller.PostprocessingArguments)
        {
            var editButton = Gtk.Button.NewFromIconName("document-edit-symbolic");
            editButton.Valign = Gtk.Align.Center;
            editButton.TooltipText = _translationService._("Edit");
            editButton.AddCssClass("flat");
            editButton.OnClicked += (_, _) => EditPostProcessingArgument(argument.Name);
            var deleteButton = Gtk.Button.NewFromIconName("user-trash-symbolic");
            deleteButton.Valign = Gtk.Align.Center;
            deleteButton.TooltipText = _translationService._("Delete");
            deleteButton.AddCssClass("flat");
            deleteButton.OnClicked += (_, _) => DeletePostProcessingArgument(argument.Name);
            var row = Adw.ActionRow.New();
            row.Title = argument.Name;
            row.Subtitle = argument.Args;
            row.AddSuffix(editButton);
            row.AddSuffix(deleteButton);
            row.ActivatableWidget = editButton;
            _postprocessingArgumentRows.Add(row);
            _postprocessingArgumentsGroup!.Add(row);
        }
        if (_controller.PostprocessingArguments.Count == 0)
        {
            var row = Adw.ActionRow.New();
            row.Title = _translationService._("No Arguments");
            _postprocessingArgumentRows.Add(row);
            _postprocessingArgumentsGroup!.Add(row);
        }
    }

    private void ThemeRow_OnNotify(GObject.Object sender, NotifySignalArgs args)
    {
        if (args.Pspec.GetName() == "selected-item")
        {
            _controller.Theme = _controller.Themes[(int)_themeRow!.Selected];
            Adw.StyleManager.GetDefault().ColorScheme = _controller.Themes[(int)_themeRow!.Selected].Value switch
            {
                Theme.Light => Adw.ColorScheme.ForceLight,
                Theme.Dark => Adw.ColorScheme.ForceDark,
                _ => Adw.ColorScheme.Default
            };
        }
    }

    private async void SelectCookiesFileButton_OnClicked(Gtk.Button sender, EventArgs args)
    {
        var fileDialog = Gtk.FileDialog.New();
        fileDialog.Title = _translationService._("Select Cookies File");
        var filter = Gtk.FileFilter.New();
        filter.Name = _translationService._("TXT Files (*.txt)");
        filter.AddPattern("*.txt");
        filter.AddPattern("*.TXT");
        var filters = Gio.ListStore.New(Gtk.FileFilter.GetGType());
        filters.Append(filter);
        fileDialog.SetFilters(filters);
        try
        {
            var res = await fileDialog.OpenAsync(_parent);
            if (res is not null)
            {
                _cookiesFileRow!.Subtitle = res.GetPath();
            }
        }
        catch { }
    }

    private void ClearCookiesFileButton_OnClicked(Gtk.Button sender, EventArgs args) => _cookiesFileRow!.Subtitle = _translationService._("No file selected");

    private void AddPostprocessingArgumentButton_OnClicked(Gtk.Button sender, EventArgs args)
    {
        _postprocessingArgumentEditMode = EditMode.Add;
        _editArgumentNameRow!.Text_ = string.Empty;
        _editArgumentNameRow.Sensitive = true;
        _editArgumentPostProcessorRow!.SetModel(_controller.PostProcessors);
        _editArgumentExecutableRow!.SetModel(_controller.Executables);
        _editArgumentArgsRow!.Text_ = string.Empty;
        _editConfirmPostprocessingArgumentButton!.Label = _translationService._("Add");
        _editPostprocessingArgumentDialog!.Present(this);
    }

    private async void EditConfirmPostprocessingArgumentButton_OnClicked(Gtk.Button sender, EventArgs args)
    {
        string? error = null;
        switch (_postprocessingArgumentEditMode)
        {
            case EditMode.Add:
                error = await _controller.AddPostprocessingArgumentAsync(
                    _editArgumentNameRow!.Text_ ?? string.Empty,
                    _controller.PostProcessors[(int)_editArgumentPostProcessorRow!.Selected],
                    _controller.Executables[(int)_editArgumentExecutableRow!.Selected],
                    _editArgumentArgsRow!.Text_ ?? string.Empty);
                break;
            case EditMode.Edit:
                error = await _controller.UpdatePostprocessingArgumentAsync(
                    _editArgumentNameRow!.Text_ ?? string.Empty,
                    _controller.PostProcessors[(int)_editArgumentPostProcessorRow!.Selected],
                    _controller.Executables[(int)_editArgumentExecutableRow!.Selected],
                    _editArgumentArgsRow!.Text_ ?? string.Empty);
                break;
            default:
                break;
        }
        if (error is not null)
        {
            var alert = Adw.AlertDialog.New(_translationService._("Error"), error);
            alert.AddResponse("ok", _translationService._("OK"));
            alert.SetDefaultResponse("ok");
            alert.Present(this);
        }
        else
        {
            _editPostprocessingArgumentDialog!.ForceClose();
        }
    }

    private void EditPostProcessingArgument(string name)
    {
        var argument = _controller.PostprocessingArguments.FirstOrDefault(a => a.Name == name);
        if (argument is null)
        {
            return;
        }
        _postprocessingArgumentEditMode = EditMode.Edit;
        _editArgumentNameRow!.Text_ = argument.Name;
        _editArgumentNameRow.Sensitive = false;
        _editArgumentPostProcessorRow!.SetModel(_controller.PostProcessors);
        _editArgumentPostProcessorRow!.Selected = (uint)_controller.PostProcessors.Select((x, i) => (x, i)).Where(x => x.x.Value == argument.PostProcessor).First().i;
        _editArgumentExecutableRow!.SetModel(_controller.Executables);
        _editArgumentExecutableRow!.Selected = (uint)_controller.Executables.Select((x, i) => (x, i)).Where(x => x.x.Value == argument.Executable).First().i;
        _editArgumentArgsRow!.Text_ = argument.Args;
        _editConfirmPostprocessingArgumentButton!.Label = _translationService._("Edit");
        _editPostprocessingArgumentDialog!.Present(this);
    }

    private void DeletePostProcessingArgument(string name)
    {
        var dialog = Adw.AlertDialog.New(_translationService._("Delete Argument?"), _translationService._("Are you sure you want to delete this post-processor argument? This action is irreversible"));
        dialog.AddResponse("delete", _translationService._("Delete"));
        dialog.AddResponse("cancel", _translationService._("Cancel"));
        dialog.SetResponseAppearance("delete", Adw.ResponseAppearance.Destructive);
        dialog.SetDefaultResponse("cancel");
        dialog.SetCloseResponse("cancel");
        dialog.OnResponse += async (_, e) =>
        {
            if (e.Response == "delete")
            {
                await _controller.DeletePostprocessingArgumentAsync(name);
            }
        };
        dialog.Present(this);
    }
}
