using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using AmigaDB.VideoRenderer.Models;
using AmigaDB.VideoRenderer.Services;
using Microsoft.Win32;
using System.Windows.Media;

namespace AmigaDB.VideoRenderer;

public partial class MainWindow : Window
{
    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 MainWindow()
    {
        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;
        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);
        Closing += (_, _) => SaveSettings();
    }

    private void AddInputs_Click(object sender, RoutedEventArgs e)
    {
        OpenFileDialog dialog = new() { Filter = "AVI recordings (*.avi)|*.avi", Multiselect = true };
        if (dialog.ShowDialog(this) != true) return;
        foreach (string file in dialog.FileNames.OrderBy(NaturalKey))
            if (!_inputs.Contains(file, StringComparer.OrdinalIgnoreCase)) _inputs.Add(file);
    }

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

    private void BrowseEndCard_Click(object sender, RoutedEventArgs e)
    {
        OpenFileDialog dialog = new() { Filter = "Images|*.png;*.jpg;*.jpeg;*.webp;*.bmp|All files|*.*" };
        if (dialog.ShowDialog(this) == true) EndCardBox.Text = dialog.FileName;
    }

    private void BrowseOutput_Click(object sender, RoutedEventArgs e)
    {
        OpenFolderDialog dialog = new() { InitialDirectory = OutputFolderBox.Text };
        if (dialog.ShowDialog(this) == true) OutputFolderBox.Text = dialog.FolderName;
    }

    private void BrowseFfmpeg_Click(object sender, RoutedEventArgs e)
    {
        OpenFileDialog dialog = new() { Filter = "FFmpeg executable (ffmpeg.exe)|ffmpeg.exe|Executable files (*.exe)|*.exe|All files|*.*" };
        if (dialog.ShowDialog(this) == true) FfmpegPathBox.Text = dialog.FileName;
    }

    private void BrowseFfprobe_Click(object sender, RoutedEventArgs e)
    {
        OpenFileDialog dialog = new() { Filter = "FFprobe executable (ffprobe.exe)|ffprobe.exe|Executable files (*.exe)|*.exe|All files|*.*" };
        if (dialog.ShowDialog(this) == true) FfprobePathBox.Text = dialog.FileName;
    }

    private void BrowsePreviewPlayer_Click(object sender, RoutedEventArgs e)
    {
        OpenFileDialog dialog = new() { Filter = "Video player (*.exe)|*.exe|All files|*.*" };
        if (dialog.ShowDialog(this) == true) PreviewPlayerPathBox.Text = dialog.FileName;
    }

    private void OpenSettings_Click(object sender, RoutedEventArgs e) => SettingsOverlay.Visibility = Visibility.Visible;

    private void CloseSettings_Click(object sender, RoutedEventArgs e)
    {
        SettingsOverlay.Visibility = Visibility.Collapsed;
        SaveSettings();
    }

    private async void Render_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            RenderSettings settings = ReadSettings();
            SaveSettings();
            SetRendering(true);
            LogBox.Clear();
            _renderCancellation = new CancellationTokenSource();
            Progress<RenderProgress> progress = new(UpdateProgress);
            await new RenderPipeline().RenderAsync(settings, progress, AppendLog, _renderCancellation.Token);
            _lastRenderedFile = Path.Combine(settings.OutputDirectory, settings.OutputName + "_final.mp4");
            OpenOutputButton.IsEnabled = true;
            PreviewRenderedButton.IsEnabled = File.Exists(_lastRenderedFile);
            MessageBox.Show(this, "The AmiReel render completed successfully.", "Render complete",
                MessageBoxButton.OK, MessageBoxImage.Information);
        }
        catch (OperationCanceledException)
        {
            UpdateProgress(new(RenderProgress.Value, "Cancelled", "The render was cancelled."));
        }
        catch (Exception exception)
        {
            AppendLog("ERROR: " + exception);
            MessageBox.Show(this, UserFacingErrors.Summarize(exception), "Render failed", MessageBoxButton.OK, MessageBoxImage.Error);
            StageText.Text = "Failed";
        }
        finally
        {
            _renderCancellation?.Dispose();
            _renderCancellation = null;
            SetRendering(false);
        }
    }

    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>(((ComboBoxItem)EncoderBox.SelectedItem).Tag!.ToString()!);
        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);
    }

    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 void PreviewSource_Click(object sender, RoutedEventArgs e)
    {
        if (InputList.SelectedItem is string path)
            OpenPreview(path);
    }

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

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

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

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

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

            _logFlushScheduled = true;
        }

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

            if (chunk.Length == 0)
                return;

            LogBox.AppendText(chunk);
            LogBox.ScrollToEnd();
        });
    }

    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 void ThemeBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (!IsLoaded) return;
        string theme = SelectedTheme();
        ApplyTheme(theme);
        SaveSettings();
    }

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

    private void OpenPreview(string mediaPath)
    {
        if (!File.Exists(mediaPath))
        {
            MessageBox.Show(this, "The selected media file was not found.", "Preview unavailable",
                MessageBoxButton.OK, MessageBoxImage.Warning);
            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)
        {
            MessageBox.Show(this, exception.Message, "Preview failed", MessageBoxButton.OK, MessageBoxImage.Error);
        }
    }

    private string SelectedTheme() => ((ComboBoxItem)ThemeBox.SelectedItem).Tag!.ToString()!;
    private string SelectedEncoder() => ((ComboBoxItem)EncoderBox.SelectedItem).Tag!.ToString()!;

    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 ApplyTheme(string theme)
    {
        bool light = string.Equals(theme, "Light", StringComparison.OrdinalIgnoreCase);

        SetBrush("PageBrush", light ? "#F4F7FB" : "#0E1525");
        SetBrush("PanelBrush", light ? "#FFFFFF" : "#162033");
        SetBrush("PanelAltBrush", light ? "#EEF4FB" : "#1A2740");
        SetBrush("FieldBrush", light ? "#F7FAFD" : "#1D2940");
        SetBrush("BorderBrush", light ? "#C7D3E3" : "#2B3A58");
        SetBrush("AccentBrush", light ? "#0E9AEF" : "#4AB8FF");
        SetBrush("AccentSoftBrush", light ? "#D6EEFF" : "#13324F");
        SetBrush("TextBrush", light ? "#122033" : "#F5F7FB");
        SetBrush("MutedBrush", light ? "#5F7390" : "#9FB1CC");
        SetBrush("ButtonBrush", light ? "#E5EDF7" : "#243554");
        SetBrush("ButtonHoverBrush", light ? "#D7E4F4" : "#2E446C");
        SetBrush("ButtonDisabledBrush", light ? "#EEF2F7" : "#2A3140");
        SetBrush("ButtonDisabledTextBrush", light ? "#8C99AB" : "#7E8BA1");
        SetBrush("PrimaryTextBrush", light ? "#FFFFFF" : "#07111D");
        SetBrush("SelectionBrush", light ? "#BEE4FF" : "#295C87");
        SetBrush("SelectionTextBrush", light ? "#122033" : "#FFFFFF");
    }

    private void SetBrush(string key, string color)
    {
        Application.Current.Resources[key] = new SolidColorBrush((Color)ColorConverter.ConvertFromString(color));
    }
}
