using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using AmiReel.Models;
using AmiReel.Services;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.ApplicationModel.DataTransfer;
using Windows.Storage;
using Windows.Storage.Pickers;
using WinRT.Interop;

namespace AmiReel;

public sealed partial class MainPage : Page
{
    private readonly ObservableCollection<string> _inputs = [];
    private readonly AppSettings _loadedSettings;
    private readonly StringBuilder _pendingLog = new();
    private readonly object _logLock = new();
    private CancellationTokenSource? _renderCancellation;
    private string? _lastRenderedFile;
    private bool _logFlushScheduled;
    private bool _isShortsTab;

    public MainPage()
    {
        InitializeComponent();
        InputList.ItemsSource = _inputs;
        _loadedSettings = AppSettingsStore.Load();
        OutputFolderBox.Text = _loadedSettings.OutputFolder;
        EndCardBox.Text = _loadedSettings.EndCardPath;
        FfmpegPathBox.Text = _loadedSettings.FfmpegPath;
        FfprobePathBox.Text = _loadedSettings.FfprobePath;
        PreviewPlayerPathBox.Text = _loadedSettings.PreviewPlayerPath;
        OutputNameBox.Text = "amigadb_intro";
        TrimBox.Text = _loadedSettings.TrimStart;
        FadeBox.Text = _loadedSettings.FadeSeconds;
        HoldBox.Text = _loadedSettings.EndCardHoldSeconds;
        IntervalBox.Text = _loadedSettings.ThumbnailInterval;
        SelectEncoder(_loadedSettings.Encoder);
        SelectTheme(_loadedSettings.Theme);
        ApplyTheme(_loadedSettings.Theme);
        MoveSourcesBox.IsChecked = _loadedSettings.ShouldMoveSourcesToOriginals;

        ShortHookBox.Text = _loadedSettings.ShortsHook;
        ShortWebsiteBox.Text = _loadedSettings.ShortsWebsite;
        ShortCrfBox.Text = _loadedSettings.ShortsCrf;
        ShortBackgroundBox.Text = _loadedSettings.ShortsBackgroundImage;
        ShortFontBox.Text = string.IsNullOrWhiteSpace(_loadedSettings.ShortsFontFile)
            ? ShortsPipeline.FindDefaultFont() ?? ""
            : _loadedSettings.ShortsFontFile;
        SelectComboTag(ShortStyleBox, _loadedSettings.ShortsStyle);
        SelectComboTag(ShortPresetBox, _loadedSettings.ShortsPreset);
        UpdateShortBackgroundVisibility();
    }

    private void VideoTab_Click(object sender, RoutedEventArgs e) => SetActiveTab(shorts: false);

    private void ShortsTab_Click(object sender, RoutedEventArgs e) => SetActiveTab(shorts: true);

    private void SetActiveTab(bool shorts)
    {
        _isShortsTab = shorts;
        VideoContentPanel.Visibility = shorts ? Visibility.Collapsed : Visibility.Visible;
        ShortsContentPanel.Visibility = shorts ? Visibility.Visible : Visibility.Collapsed;
        Style primaryButtonStyle = (Style)Application.Current.Resources["PrimaryButtonStyle"];
        VideoTabButton.Style = shorts ? null : primaryButtonStyle;
        ShortsTabButton.Style = shorts ? primaryButtonStyle : null;
        RenderButton.Content = shorts ? "Render short" : "Start render";
    }

    private void ShortStyleBox_SelectionChanged(object sender, SelectionChangedEventArgs e) => UpdateShortBackgroundVisibility();

    private void UpdateShortBackgroundVisibility()
    {
        bool isBrand = string.Equals(SelectedShortStyle(), nameof(ShortStyle.Brand), StringComparison.OrdinalIgnoreCase);
        ShortBackgroundRow.Visibility = isBrand ? Visibility.Visible : Visibility.Collapsed;
    }

    private async void AddInputs_Click(object sender, RoutedEventArgs e)
    {
        FileOpenPicker picker = CreateFileOpenPicker();
        foreach (string extension in SupportedVideoFormats.Extensions)
            picker.FileTypeFilter.Add(extension);
        picker.SuggestedStartLocation = PickerLocationId.VideosLibrary;
        var files = await picker.PickMultipleFilesAsync();
        if (files is null) return;

        foreach (string path in files.Select(file => file.Path).OrderBy(NaturalKey))
            if (!_inputs.Contains(path, StringComparer.OrdinalIgnoreCase)) _inputs.Add(path);
    }

    private void ClearInputs_Click(object sender, RoutedEventArgs e) => _inputs.Clear();

    private void InputList_DragOver(object sender, DragEventArgs e)
    {
        e.AcceptedOperation = e.DataView.Contains(StandardDataFormats.StorageItems)
            ? DataPackageOperation.Copy
            : DataPackageOperation.None;
    }

    private async void InputList_Drop(object sender, DragEventArgs e)
    {
        if (!e.DataView.Contains(StandardDataFormats.StorageItems)) return;

        DragOperationDeferral deferral = e.GetDeferral();
        try
        {
            IReadOnlyList<IStorageItem> items = await e.DataView.GetStorageItemsAsync();
            foreach (string path in items.OfType<StorageFile>()
                         .Select(file => file.Path)
                         .Where(SupportedVideoFormats.IsSupported)
                         .OrderBy(NaturalKey))
                if (!_inputs.Contains(path, StringComparer.OrdinalIgnoreCase)) _inputs.Add(path);
        }
        finally
        {
            deferral.Complete();
        }
    }

    private async void BrowseEndCard_Click(object sender, RoutedEventArgs e)
    {
        FileOpenPicker picker = CreateFileOpenPicker();
        picker.FileTypeFilter.Add(".png");
        picker.FileTypeFilter.Add(".jpg");
        picker.FileTypeFilter.Add(".jpeg");
        picker.FileTypeFilter.Add(".webp");
        picker.FileTypeFilter.Add(".bmp");
        var file = await picker.PickSingleFileAsync();
        if (file is not null)
            EndCardBox.Text = file.Path;
    }

    private async void BrowseOutput_Click(object sender, RoutedEventArgs e)
    {
        FolderPicker picker = CreateFolderPicker();
        var folder = await picker.PickSingleFolderAsync();
        if (folder is not null)
            OutputFolderBox.Text = folder.Path;
    }

    private async void BrowseFfmpeg_Click(object sender, RoutedEventArgs e)
    {
        FileOpenPicker picker = CreateFileOpenPicker();
        picker.FileTypeFilter.Add(".exe");
        var file = await picker.PickSingleFileAsync();
        if (file is not null)
            FfmpegPathBox.Text = file.Path;
    }

    private async void BrowseFfprobe_Click(object sender, RoutedEventArgs e)
    {
        FileOpenPicker picker = CreateFileOpenPicker();
        picker.FileTypeFilter.Add(".exe");
        var file = await picker.PickSingleFileAsync();
        if (file is not null)
            FfprobePathBox.Text = file.Path;
    }

    private async void BrowsePreviewPlayer_Click(object sender, RoutedEventArgs e)
    {
        FileOpenPicker picker = CreateFileOpenPicker();
        picker.FileTypeFilter.Add(".exe");
        var file = await picker.PickSingleFileAsync();
        if (file is not null)
            PreviewPlayerPathBox.Text = file.Path;
    }

    private async void OpenSettings_Click(object sender, RoutedEventArgs e)
    {
        SettingsDialog.XamlRoot = XamlRoot;
        await SettingsDialog.ShowAsync();
        SaveSettings();
    }

    private async void Render_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            SetRendering(true);
            LogBox.Text = string.Empty;
            _renderCancellation = new CancellationTokenSource();

            if (_isShortsTab)
                await RenderShortAsync(_renderCancellation.Token);
            else
                await RenderVideoAsync(_renderCancellation.Token);
        }
        catch (OperationCanceledException)
        {
            UpdateProgress(new(RenderProgressBar.Value, "Cancelled", "The render was cancelled."));
        }
        catch (Exception exception)
        {
            AppendLog("ERROR: " + exception);
            StageText.Text = "Failed";
            await ShowMessageAsync("Render failed", UserFacingErrors.Summarize(exception));
        }
        finally
        {
            _renderCancellation?.Dispose();
            _renderCancellation = null;
            SetRendering(false);
        }
    }

    private async Task RenderVideoAsync(CancellationToken token)
    {
        RenderSettings settings = ReadSettings();
        SaveSettings();
        Progress<RenderProgress> progress = new(UpdateProgress);
        IReadOnlyList<string> resolvedInputs = await new RenderPipeline().RenderAsync(settings, progress, AppendLog, token);
        ReplaceInputs(resolvedInputs);
        _lastRenderedFile = Path.Combine(settings.OutputDirectory, settings.OutputName + "_final.mp4");
        OpenOutputButton.IsEnabled = true;
        PreviewRenderedButton.IsEnabled = File.Exists(_lastRenderedFile);
        await ShowMessageAsync("Render complete", "The AmiReel render completed successfully.");
    }

    private async Task RenderShortAsync(CancellationToken token)
    {
        ShortSettings settings = ReadShortSettings();
        SaveSettings();
        Progress<RenderProgress> progress = new(UpdateProgress);
        await new ShortsPipeline().RenderAsync(settings, progress, AppendLog, token);
        _lastRenderedFile = settings.OutputPath;
        ShortOutputBox.Text = settings.OutputPath;
        OpenOutputButton.IsEnabled = true;
        PreviewRenderedButton.IsEnabled = File.Exists(_lastRenderedFile);
        await ShowMessageAsync("Short complete", "The YouTube Short was rendered successfully.");
    }

    private void Cancel_Click(object sender, RoutedEventArgs e)
    {
        CancelButton.IsEnabled = false;
        StatusText.Text = "Stopping FFmpeg...";
        _renderCancellation?.Cancel();
    }

    private void OpenOutput_Click(object sender, RoutedEventArgs e)
    {
        string? directory = !string.IsNullOrWhiteSpace(_lastRenderedFile)
            ? Path.GetDirectoryName(_lastRenderedFile)
            : OutputFolderBox.Text;
        if (!string.IsNullOrWhiteSpace(directory) && Directory.Exists(directory))
            Process.Start(new ProcessStartInfo("explorer.exe", directory) { UseShellExecute = true });
    }

    private async void PreviewSource_Click(object sender, RoutedEventArgs e)
    {
        if (InputList.SelectedItem is string path)
            await OpenPreviewAsync(path);
    }

    private async void PreviewRendered_Click(object sender, RoutedEventArgs e)
    {
        if (!string.IsNullOrWhiteSpace(_lastRenderedFile) && File.Exists(_lastRenderedFile))
            await OpenPreviewAsync(_lastRenderedFile);
    }

    private void ThemeBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (!IsLoaded) return;
        string theme = SelectedTheme();
        ApplyTheme(theme);
        SaveSettings();
    }

    private RenderSettings ReadSettings()
    {
        static double Number(string text, string name)
        {
            if (!double.TryParse(text.Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out double value) || value < 0)
                throw new ArgumentException($"Enter a valid non-negative value for {name}.");
            return value;
        }

        if (!int.TryParse(IntervalBox.Text, out int interval) || interval < 1)
            throw new ArgumentException("Thumbnail interval must be at least one second.");

        EncoderMode encoder = Enum.Parse<EncoderMode>(SelectedEncoder());
        return new RenderSettings
        {
            InputFiles = _inputs.ToList(),
            EndCardPath = EndCardBox.Text.Trim(),
            OutputDirectory = OutputFolderBox.Text.Trim(),
            OutputName = OutputNameBox.Text.Trim(),
            FfmpegPath = FfmpegPathBox.Text.Trim(),
            FfprobePath = FfprobePathBox.Text.Trim(),
            Encoder = encoder,
            TrimStart = Number(TrimBox.Text, "trim start"),
            FadeSeconds = Number(FadeBox.Text, "fade"),
            EndCardHoldSeconds = Number(HoldBox.Text, "end-card hold"),
            ThumbnailInterval = interval,
            MoveSourcesToOriginals = MoveSourcesBox.IsChecked == true,
        };
    }

    private void ReplaceInputs(IReadOnlyList<string> paths)
    {
        _inputs.Clear();
        foreach (string path in paths)
            _inputs.Add(path);
    }

    private ShortSettings ReadShortSettings()
    {
        if (!double.TryParse(ShortDurationBox.Text.Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out double duration) || duration <= 0)
            throw new ArgumentException("Enter a valid duration in seconds.");
        if (!int.TryParse(ShortCrfBox.Text, out int crf))
            throw new ArgumentException("CRF must be an integer.");

        string input = ShortInputBox.Text.Trim();
        string output = ShortOutputBox.Text.Trim();
        ShortStyle style = Enum.Parse<ShortStyle>(SelectedShortStyle());
        if (string.IsNullOrWhiteSpace(output) && !string.IsNullOrWhiteSpace(input))
        {
            string baseName = Path.GetFileNameWithoutExtension(input);
            string? directory = Path.GetDirectoryName(input);
            output = Path.Combine(directory ?? "", $"{baseName}-{style.ToString().ToLowerInvariant()}-short.mp4");
        }

        return new ShortSettings
        {
            InputFile = input,
            OutputPath = output,
            Title = ShortTitleBox.Text.Trim(),
            Group = ShortGroupBox.Text.Trim(),
            Year = ShortYearBox.Text.Trim(),
            Type = ShortTypeBox.Text.Trim(),
            Hook = ShortHookBox.Text.Trim(),
            Website = ShortWebsiteBox.Text.Trim(),
            Start = string.IsNullOrWhiteSpace(ShortStartBox.Text) ? "00:00:00" : ShortStartBox.Text.Trim(),
            DurationSeconds = duration,
            Style = style,
            BackgroundImage = ShortBackgroundBox.Text.Trim(),
            FontFile = ShortFontBox.Text.Trim(),
            FfmpegPath = FfmpegPathBox.Text.Trim(),
            FfprobePath = FfprobePathBox.Text.Trim(),
            Crf = crf,
            Preset = SelectedShortPreset(),
        };
    }

    private async void BrowseShortInput_Click(object sender, RoutedEventArgs e)
    {
        FileOpenPicker picker = CreateFileOpenPicker();
        foreach (string extension in SupportedVideoFormats.Extensions)
            picker.FileTypeFilter.Add(extension);
        picker.SuggestedStartLocation = PickerLocationId.VideosLibrary;
        var file = await picker.PickSingleFileAsync();
        if (file is not null)
            ShortInputBox.Text = file.Path;
    }

    private async void BrowseShortOutput_Click(object sender, RoutedEventArgs e)
    {
        FileSavePicker picker = new();
        picker.FileTypeChoices.Add("MP4 video", [".mp4"]);
        picker.SuggestedFileName = "short.mp4";
        InitializeWithWindow.Initialize(picker, App.MainWindowHandle);
        var file = await picker.PickSaveFileAsync();
        if (file is not null)
            ShortOutputBox.Text = file.Path;
    }

    private async void BrowseShortBackground_Click(object sender, RoutedEventArgs e)
    {
        FileOpenPicker picker = CreateFileOpenPicker();
        picker.FileTypeFilter.Add(".png");
        picker.FileTypeFilter.Add(".jpg");
        picker.FileTypeFilter.Add(".jpeg");
        var file = await picker.PickSingleFileAsync();
        if (file is not null)
            ShortBackgroundBox.Text = file.Path;
    }

    private async void BrowseShortFont_Click(object sender, RoutedEventArgs e)
    {
        FileOpenPicker picker = CreateFileOpenPicker();
        picker.FileTypeFilter.Add(".ttf");
        picker.FileTypeFilter.Add(".otf");
        var file = await picker.PickSingleFileAsync();
        if (file is not null)
            ShortFontBox.Text = file.Path;
    }

    private async void PreviewShortSource_Click(object sender, RoutedEventArgs e)
    {
        if (!string.IsNullOrWhiteSpace(ShortInputBox.Text))
            await OpenPreviewAsync(ShortInputBox.Text.Trim());
    }

    private void SetRendering(bool rendering)
    {
        RenderButton.IsEnabled = !rendering;
        CancelButton.IsEnabled = rendering;
        OpenSettingsButton.IsEnabled = !rendering;
        VideoTabButton.IsEnabled = !rendering;
        ShortsTabButton.IsEnabled = !rendering;
    }

    private void UpdateProgress(RenderProgress value)
    {
        RenderProgressBar.Value = value.Percent;
        PercentText.Text = $"{value.Percent:0}%";
        StageText.Text = value.Stage;
        StatusText.Text = value.Message;
    }

    private async Task OpenPreviewAsync(string mediaPath)
    {
        if (!File.Exists(mediaPath))
        {
            await ShowMessageAsync("Preview unavailable", "The selected media file was not found.");
            return;
        }

        string playerPath = PreviewPlayerPathBox.Text.Trim();

        try
        {
            if (!string.IsNullOrWhiteSpace(playerPath))
            {
                if (!File.Exists(playerPath))
                    throw new FileNotFoundException("Configured preview player was not found.", playerPath);

                ProcessStartInfo customPlayer = new()
                {
                    FileName = playerPath,
                    UseShellExecute = false
                };
                customPlayer.ArgumentList.Add(mediaPath);
                Process.Start(customPlayer);
                return;
            }

            Process.Start(new ProcessStartInfo(mediaPath) { UseShellExecute = true });
        }
        catch (Exception exception)
        {
            await ShowMessageAsync("Preview failed", exception.Message);
        }
    }

    private void AppendLog(string line)
    {
        lock (_logLock)
        {
            _pendingLog.AppendLine(line);
            if (_logFlushScheduled)
                return;

            _logFlushScheduled = true;
        }

        _ = DispatcherQueue.TryEnqueue(() =>
        {
            string chunk;
            lock (_logLock)
            {
                chunk = _pendingLog.ToString();
                _pendingLog.Clear();
                _logFlushScheduled = false;
            }

            if (chunk.Length == 0)
                return;

            LogBox.Text += chunk;
            LogBox.Select(LogBox.Text.Length, 0);
        });
    }

    private void SaveSettings()
    {
        AppSettingsStore.Save(new AppSettings
        {
            OutputFolder = OutputFolderBox.Text.Trim(),
            EndCardPath = EndCardBox.Text.Trim(),
            FfmpegPath = FfmpegPathBox.Text.Trim(),
            FfprobePath = FfprobePathBox.Text.Trim(),
            PreviewPlayerPath = PreviewPlayerPathBox.Text.Trim(),
            Theme = SelectedTheme(),
            Encoder = SelectedEncoder(),
            TrimStart = TrimBox.Text.Trim(),
            FadeSeconds = FadeBox.Text.Trim(),
            EndCardHoldSeconds = HoldBox.Text.Trim(),
            ThumbnailInterval = IntervalBox.Text.Trim(),
            MoveSourcesToOriginals = MoveSourcesBox.IsChecked == true,
            ShortsStyle = SelectedShortStyle(),
            ShortsHook = ShortHookBox.Text.Trim(),
            ShortsWebsite = ShortWebsiteBox.Text.Trim(),
            ShortsFontFile = ShortFontBox.Text.Trim(),
            ShortsBackgroundImage = ShortBackgroundBox.Text.Trim(),
            ShortsCrf = ShortCrfBox.Text.Trim(),
            ShortsPreset = SelectedShortPreset(),
        });
    }

    private string SelectedTheme() => (ThemeBox.SelectedItem as ComboBoxItem)?.Tag?.ToString() ?? "Dark";

    private string SelectedEncoder() => (EncoderBox.SelectedItem as ComboBoxItem)?.Tag?.ToString() ?? "Auto";

    private string SelectedShortStyle() => (ShortStyleBox.SelectedItem as ComboBoxItem)?.Tag?.ToString() ?? "Brand";

    private string SelectedShortPreset() => (ShortPresetBox.SelectedItem as ComboBoxItem)?.Tag?.ToString() ?? "slow";

    private void SelectTheme(string theme) => SelectComboTag(ThemeBox, theme);

    private void SelectEncoder(string encoder) => SelectComboTag(EncoderBox, encoder);

    private static void SelectComboTag(ComboBox comboBox, string tag)
    {
        foreach (ComboBoxItem item in comboBox.Items.Cast<ComboBoxItem>())
        {
            if (string.Equals(item.Tag?.ToString(), tag, StringComparison.OrdinalIgnoreCase))
            {
                comboBox.SelectedItem = item;
                return;
            }
        }

        comboBox.SelectedIndex = 0;
    }

    private void InputList_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        PreviewSourceButton.IsEnabled = InputList.SelectedItem is string;
    }

    private void ApplyTheme(string theme)
    {
        bool isDark = !string.Equals(theme, "Light", StringComparison.OrdinalIgnoreCase);
        App.MainWindowInstance?.ApplyTheme(isDark);
    }

    private FileOpenPicker CreateFileOpenPicker()
    {
        FileOpenPicker picker = new();
        InitializeWithWindow.Initialize(picker, App.MainWindowHandle);
        return picker;
    }

    private FolderPicker CreateFolderPicker()
    {
        FolderPicker picker = new();
        picker.FileTypeFilter.Add("*");
        InitializeWithWindow.Initialize(picker, App.MainWindowHandle);
        return picker;
    }

    private static string NaturalKey(string path)
    {
        string name = Path.GetFileNameWithoutExtension(path);
        int underscore = name.LastIndexOf('_');
        return underscore >= 0 && int.TryParse(name[(underscore + 1)..], out int number)
            ? name[..underscore] + number.ToString("D10")
            : name;
    }

    private async Task ShowMessageAsync(string title, string message)
    {
        ContentDialog dialog = DialogHelper.CreateStyled(XamlRoot, ActualTheme);
        dialog.Title = title;
        dialog.Content = message;
        dialog.CloseButtonText = "OK";
        await dialog.ShowAsync();
    }
}
