using InABox.Clients;
using InABox.Core;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using InABox.WPF;

namespace InABox.Wpf;

public enum MessageWindowButtonPosition
{
    Left,
    Right
}

public enum MessageWindowResult
{
    None,
    OK,
    Cancel,
    Yes,
    No,
    Other
}

public class MessageWindowButtonDelegateArgs : EventArgs
{
    public MessageWindowButton Button { get; private set; }
    public bool Close { get; set; } = false;

    public MessageWindowButtonDelegateArgs(MessageWindowButton button, bool close)
    {
        Button = button;
        Close = close;
    }
    
}

public delegate void MessageWindowButtonDelegate(MessageWindow window, MessageWindowButtonDelegateArgs args);


public class MessageWindowButton : INotifyPropertyChanged
{

    public MessageWindowButtonPosition Position { get; set; }

    private string _content;
    public string Content
    {
        get => _content;
        [MemberNotNull(nameof(_content))]
        set
        {
            _content = value;
            OnPropertyChanged();
        }
    }

    public MessageWindowButtonDelegate Action { get; set; }

    public MessageWindowButton(string content, MessageWindowButtonDelegate action, MessageWindowButtonPosition position)
    {
        Content = content;
        Action = action;
        Position = position;
    }

    public event PropertyChangedEventHandler? PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

/// <summary>
/// Interaction logic for MessageWindow.xaml
/// </summary>
public partial class MessageWindow : Window, INotifyPropertyChanged
{
    public ObservableCollection<MessageWindowButton> Buttons { get; private set; } = new();

    public IEnumerable<MessageWindowButton> LeftButtons => Buttons.Where(x => x.Position == MessageWindowButtonPosition.Left);
    public IEnumerable<MessageWindowButton> RightButtons => Buttons.Where(x => x.Position == MessageWindowButtonPosition.Right);

    private string _message = "";
    public string Message
    {
        get => _message;
        set
        {
            _message = value;
            OnPropertyChanged();
        }
    }

    public ImageSource? _image = null;
    public ImageSource? Image
    {
        get => _image;
        set
        {
            _image = value;
            OnPropertyChanged();
        }
    }

    private string _details = "";

    public string Details
    {
        get => _details;
        set
        {
            _details = value;
            OnPropertyChanged();
        }
    }

    public static readonly DependencyProperty ShowDetailsProperty = DependencyProperty.Register(nameof(ShowDetails), typeof(bool), typeof(MessageWindow));

    public bool ShowDetails
    {
        get => (bool)GetValue(ShowDetailsProperty);
        set => SetValue(ShowDetailsProperty, value);
    }

    public MessageWindowResult Result { get; set; } = MessageWindowResult.None;

    public object? OtherResult { get; set; }

    public MessageWindow()
    {
        InitializeComponent();

        Buttons.CollectionChanged += Buttons_CollectionChanged;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        if (sender is not Button button || button.Tag is not MessageWindowButton winButton) return;

        var args = new MessageWindowButtonDelegateArgs(winButton, false);
        winButton.Action(this, args);
        if (args.Close)
            Close();
    }

    private void Buttons_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        OnPropertyChanged(nameof(LeftButtons));
        OnPropertyChanged(nameof(RightButtons));
    }

    public MessageWindow AddButton(MessageWindowButton button)
    {
        Buttons.Add(button);
        return this;
    }

    public MessageWindow AddOKButton(string content = "OK")
    {
        Buttons.Add(new MessageWindowButton(content, OKButton_Click, MessageWindowButtonPosition.Right));
        return this;
    }

    public MessageWindow AddCancelButton(string content = "Cancel")
    {
        Buttons.Add(new MessageWindowButton(content, CancelButton_Click, MessageWindowButtonPosition.Right));
        return this;
    }

    public MessageWindow AddYesButton(string content = "Yes")
    {
        Buttons.Add(new MessageWindowButton(content, YesButton_Click, MessageWindowButtonPosition.Right));
        return this;
    }

    public MessageWindow AddNoButton(string content = "No")
    {
        Buttons.Add(new MessageWindowButton(content, NoButton_Click, MessageWindowButtonPosition.Right));
        return this;
    }

    private void YesButton_Click(MessageWindow window, MessageWindowButtonDelegateArgs args)
    {
        Result = MessageWindowResult.Yes;
        Close();
    }

    private void NoButton_Click(MessageWindow window, MessageWindowButtonDelegateArgs args)
    {
        Result = MessageWindowResult.No;
        Close();
    }

    private void CancelButton_Click(MessageWindow window, MessageWindowButtonDelegateArgs args)
    {
        Result = MessageWindowResult.Cancel;
        Close();
    }

    private void OKButton_Click(MessageWindow window, MessageWindowButtonDelegateArgs args)
    {
        Result = MessageWindowResult.OK;
        Close();
    }

    public event PropertyChangedEventHandler? PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    #region Static Constructors

    private static readonly BitmapImage _warning = InABox.Wpf.Resources.warning.AsBitmapImage();

    public static BitmapImage WarningImage => _warning;

    private static readonly BitmapImage _question = InABox.Wpf.Resources.help.AsBitmapImage();

    public static BitmapImage QuestionImage => _question;

    public static MessageWindow New()
    {
        return new MessageWindow();
    }

    public static MessageWindow NewMessage(string message, string title, ImageSource? image = null)
    {
        return new MessageWindow()
            .Title(title)
            .Message(message)
            .Image(image)
            .AddOKButton();
    }
    public static void ShowMessage(string message, string title, ImageSource? image = null)
    {
        NewMessage(message, title, image).Display();
    }

    public static MessageWindow NewWarn(string message, string title = "Warning", ImageSource? image = null)
    {
        return NewMessage(message, title, image);
    }
    public static void Warn(string message, string title = "Warning", ImageSource? image = null)
    {
        NewMessage(message, title, image).Display();
    }

    public static MessageWindow NewOKCancel(string message, string title, ImageSource? image = null)
    {
        return new MessageWindow()
            .Title(title)
            .Message(message)
            .Image(image)
            .AddOKButton()
            .AddCancelButton();
    }
    public static bool ShowOKCancel(string message, string title, ImageSource? image = null)
    {
        return NewOKCancel(message, title, image)
            .Display()
            .Result == MessageWindowResult.OK;
    }

    public static MessageWindow NewYesNo(string message, string title, ImageSource? image = null)
    {
        return new MessageWindow()
            .Title(title)
            .Message(message)
            .Image(image)
            .AddYesButton()
            .AddNoButton();
    }
    public static bool ShowYesNo(string message, string title, ImageSource? image = null)
    {
        return NewYesNo(message, title, image)
            .Display()
            .Result == MessageWindowResult.Yes;
    }
    public static MessageWindow NewYesNoCancel(string message, string title, ImageSource? image = null)
    {
        return new MessageWindow()
            .Title(title)
            .Message(message)
            .Image(image)
            .AddYesButton()
            .AddNoButton()
            .AddCancelButton();
    }
    public static MessageWindowResult ShowYesNoCancel(string message, string title, ImageSource? image = null)
    {
        return NewYesNoCancel(message, title, image)
            .Display().Result;
    }

    /// <summary>
    /// Display a message box for an exception, giving options to view the logs.
    /// </summary>
    /// <param name="message">The message to display. Set to <see langword="null"/> to default to the exception message.</param>
    /// <param name="exception"></param>
    /// <param name="title"></param>
    /// <param name="shouldLog">If <see langword="true"/>, also logs the exception.</param>
    public static MessageWindow NewError(string? message, Exception exception, string title = "Error", bool shouldLog = true, ImageSource? image = null)
    {
        if (shouldLog)
        {
            CoreUtils.LogException(ClientFactory.UserID, exception);
        }

        var window = new MessageWindow()
            .Message(message ?? exception.Message)
            .Title(title)
            .Details(CoreUtils.FormatException(exception))
            .Image(image ?? _warning)
            .AddButton(new MessageWindowButton("Email Logs", (window, args) =>
            {
                EmailLogs_Click(exception);
            }, MessageWindowButtonPosition.Left));

        var showDetailsButton = new MessageWindowButton("Show Details", (win, args) =>
        {
            win.ShowDetails = !win.ShowDetails;
            args.Button.Content = win.ShowDetails
                ? "Hide Details"
                : "Show Details";
        }, MessageWindowButtonPosition.Left);

        return window.AddButton(showDetailsButton)
            .AddOKButton();
    }

    private static void EmailLogs_Click(Exception e)
    {
        var logFile = Path.Combine(CoreUtils.GetPath(), $"{DateTime.Today:yyyy-MM-dd}.log");

        const int nRead = 1024 * 1024;

        byte[] data;
        using (var stream = File.OpenRead(logFile))
        {
            if (stream.Length > nRead)
            {
                stream.Seek(-nRead, SeekOrigin.End);
            }

            data = new BinaryReader(stream).ReadBytes(Math.Min(nRead, (int)stream.Length));
        }

        var message = EmailUtils.CreateMessage(
            subject: "Error logs",
            to: "support@prsdigital.com.au",
            body: $"Error logs for PRS:\n\nException: {CoreUtils.FormatException(e)}");
        message.AddAttachment("Error Logs.txt", data);
        EmailUtils.OpenEmail(message);
    }

    /// <summary>
    /// Display a message box for a non-exception error, giving options to view the logs.
    /// </summary>
    /// <param name="message">The message to display. Set to <see langword="null"/> to default to the exception message.</param>
    /// <param name="details"></param>
    /// <param name="title"></param>
    /// <param name="shouldLog">If <see langword="true"/>, also logs the exception.</param>
    public static MessageWindow NewError(string message, string? details = null, string title = "Error", bool shouldLog = true, ImageSource? image = null)
    {
        if (shouldLog)
        {
            Logger.Send(LogType.Error, ClientFactory.UserID, details ?? message);
        }

        var window = new MessageWindow()
            .Message(message)
            .Title(title);

        if(details is not null)
        {
            window.Details(details);
        }

        window.Image(image ?? _warning)
            .AddButton(new MessageWindowButton(
                "Email Logs",
                (window, button) => EmailLogs_Click(new Exception(details ?? message)),
                MessageWindowButtonPosition.Left));

        if(details is not null)
        {
            var showDetailsButton = new MessageWindowButton("Show Details", (win, args) =>
            {
                win.ShowDetails = !win.ShowDetails;
                args.Button.Content = win.ShowDetails
                    ? "Hide Details"
                    : "Show Details";
            }, MessageWindowButtonPosition.Left);
            window.AddButton(showDetailsButton);
        }

        return window.AddOKButton();
    }

    public static void ShowError(string? message, Exception exception, string title = "Error", bool shouldLog = true, ImageSource? image = null)
    {
        NewError(message, exception, title, shouldLog, image).Display();
    }
    public static void ShowError(string message, string details, string title = "Error", bool shouldLog = true, ImageSource? image = null)
    {
        NewError(message, details, title, shouldLog, image).Display();
    }

    private static void ShowLogs_Click(MessageWindow window, MessageWindowButton button)
    {
        var console = new MessageWindowConsole("Logs", Path.Combine(CoreUtils.GetPath(), string.Format("{0:yyyy-MM-dd}.log", DateTime.Today)));
        console.ShowDialog();
    }

    #endregion
}

public static class MessageWindowBuilder
{
    public static MessageWindow Title(this MessageWindow window, string title)
    {
        window.Title = title;
        return window;
    }

    public static MessageWindow Message(this MessageWindow window, string message)
    {
        window.Message = message;
        return window;
    }

    public static MessageWindow Image(this MessageWindow window, ImageSource? image)
    {
        window.Image = image;
        return window;
    }

    public static MessageWindow Icon(this MessageWindow window, ImageSource image)
    {
        window.Icon = image;
        return window;
    }

    public static MessageWindow Details(this MessageWindow window, string details)
    {
        window.Details = details;
        return window;
    }

    public static MessageWindow Display(this MessageWindow window)
    {
        window.ShowDialog();
        return window;
    }
}

public class MessageWindowConsole : Console.Console
{
    public string FileName { get; set; }

    public MessageWindowConsole(string description, string file) : base(description)
    {
        FileName = file;
        ConsoleControl.AllowLoadLogButton = false;
    }

    protected override void OnLoaded()
    {
        base.OnLoaded();

        if (File.Exists(FileName))
        {
            var lines = File.ReadLines(FileName);
            ConsoleControl.LoadLogEntries(lines);
        }
    }

    protected override string GetLogDirectory()
    {
        return CoreUtils.GetPath();
    }
}