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 Microsoft.Win32;
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;

    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;
    }

    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
        {
            RenderSettings settings = ReadSettings();
            SaveSettings();
            SetRendering(true);
            LogBox.Text = string.Empty;
            _renderCancellation = new CancellationTokenSource();
            Progress<RenderProgress> progress = new(UpdateProgress);
            IReadOnlyList<string> resolvedInputs = await new RenderPipeline().RenderAsync(settings, progress, AppendLog, _renderCancellation.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.");
        }
        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 void Cancel_Click(object sender, RoutedEventArgs e)
    {
        CancelButton.IsEnabled = false;
        StatusText.Text = "Stopping FFmpeg...";
        _renderCancellation?.Cancel();
    }

    private void OpenOutput_Click(object sender, RoutedEventArgs e)
    {
        if (Directory.Exists(OutputFolderBox.Text))
            Process.Start(new ProcessStartInfo("explorer.exe", OutputFolderBox.Text) { 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(
            _inputs.ToList(),
            EndCardBox.Text.Trim(),
            OutputFolderBox.Text.Trim(),
            OutputNameBox.Text.Trim(),
            FfmpegPathBox.Text.Trim(),
            FfprobePathBox.Text.Trim(),
            encoder,
            Number(TrimBox.Text, "trim start"),
            Number(FadeBox.Text, "fade"),
            Number(HoldBox.Text, "end-card hold"),
            interval,
            MoveSourcesBox.IsChecked == true);
    }

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

    private void SetRendering(bool rendering)
    {
        RenderButton.IsEnabled = !rendering;
        CancelButton.IsEnabled = rendering;
        OpenSettingsButton.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(
            OutputFolderBox.Text.Trim(),
            EndCardBox.Text.Trim(),
            FfmpegPathBox.Text.Trim(),
            FfprobePathBox.Text.Trim(),
            PreviewPlayerPathBox.Text.Trim(),
            SelectedTheme(),
            SelectedEncoder(),
            TrimBox.Text.Trim(),
            FadeBox.Text.Trim(),
            HoldBox.Text.Trim(),
            IntervalBox.Text.Trim(),
            MoveSourcesBox.IsChecked == true));
    }

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

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

    private void SelectTheme(string theme)
    {
        foreach (ComboBoxItem item in ThemeBox.Items)
        {
            if (string.Equals(item.Tag?.ToString(), theme, StringComparison.OrdinalIgnoreCase))
            {
                ThemeBox.SelectedItem = item;
                return;
            }
        }

        ThemeBox.SelectedIndex = 0;
    }

    private void SelectEncoder(string encoder)
    {
        foreach (ComboBoxItem item in EncoderBox.Items)
        {
            if (string.Equals(item.Tag?.ToString(), encoder, StringComparison.OrdinalIgnoreCase))
            {
                EncoderBox.SelectedItem = item;
                return;
            }
        }

        EncoderBox.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();
    }
}
