using ApplicationServices;
using AudibleApi.Common;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using DataLayer;
using FileLiberator;
using LibationUiBase.Forms;
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace LibationAvalonia.Dialogs;

public partial class BookRecordsDialog : DialogWindow
{
	public DataGridCollectionView DataGridCollectionView { get; }
	private readonly AvaloniaList<BookRecordEntry> bookRecordEntries = new();
	private readonly LibraryBook libraryBook;
	public BookRecordsDialog()
	{
		InitializeComponent();
		libraryBook = MockLibraryBook.CreateBook();

		if (Design.IsDesignMode)
		{
			bookRecordEntries.Add(new BookRecordEntry(new Clip(DateTimeOffset.Now.AddHours(1), TimeSpan.FromHours(6.8667), "xxxxxxx", DateTimeOffset.Now.AddHours(1), TimeSpan.FromHours(6.8668), "Note 2", "title 2")));
			bookRecordEntries.Add(new BookRecordEntry(new Clip(DateTimeOffset.Now, TimeSpan.FromHours(4.5667), "xxxxxxx", DateTimeOffset.Now, TimeSpan.FromHours(4.5668), "Note", "title")));
		}

		DataGridCollectionView = new DataGridCollectionView(bookRecordEntries);
		DataContext = this;
	}

	public BookRecordsDialog(LibraryBook libraryBook) : this()
	{
		this.libraryBook = libraryBook;
		Title = $"{libraryBook.Book.TitleWithSubtitle} - Clips and Bookmarks";

		Loaded += BookRecordsDialog_Loaded;
	}

	private async void BookRecordsDialog_Loaded(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
	{
		try
		{
			var api = await libraryBook.GetApiAsync();
			var records = await api.GetRecordsAsync(libraryBook.Book.AudibleProductId);

			bookRecordEntries.AddRange(records.Select(r => new BookRecordEntry(r)));
		}
		catch (Exception ex)
		{
			Serilog.Log.Error(ex, "Failed to retrieve records for {libraryBook}", libraryBook);
		}
	}

	#region Buttons

	private async Task setControlEnabled(object control, bool enabled)
	{
		if (control is InputElement c)
			await Dispatcher.UIThread.InvokeAsync(() => c.IsEnabled = enabled);
	}
	public async void ExportChecked_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
	{
		await setControlEnabled(sender, false);
		await saveRecords(bookRecordEntries.Where(r => r.IsChecked).Select(r => r.Record));
		await setControlEnabled(sender, true);
	}
	public async void ExportAll_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
	{
		await setControlEnabled(sender, false);
		await saveRecords(bookRecordEntries.Select(r => r.Record));
		await setControlEnabled(sender, true);
	}

	public void CheckAll()
	{
		foreach (var record in bookRecordEntries)
			record.IsChecked = true;
	}
	public void UncheckAll()
	{
		foreach (var record in bookRecordEntries)
			record.IsChecked = false;
	}
	public async void DeleteChecked_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
	{
		var records = bookRecordEntries.Where(r => r.IsChecked).Select(r => r.Record).ToList();

		if (!records.Any()) return;

		await setControlEnabled(sender, false);

		bool success = false;
		try
		{
			var api = await libraryBook.GetApiAsync();
			success = await api.DeleteRecordsAsync(libraryBook.Book.AudibleProductId, records);
			records = await api.GetRecordsAsync(libraryBook.Book.AudibleProductId);

			var removed = bookRecordEntries.ExceptBy(records, r => r.Record).ToList();

			foreach (var r in removed)
				bookRecordEntries.Remove(r);
		}
		catch (Exception ex)
		{
			Serilog.Log.Error(ex, ex.Message);
		}
		finally { await setControlEnabled(sender, true); }

		if (!success)
			await MessageBox.Show(this, $"Libation was unable to delete the {records.Count} selected records", "Deletion Failed", MessageBoxButtons.OK, MessageBoxIcon.Error);
	}

	public async void ReloadAll_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
	{
		await setControlEnabled(sender, false);
		try
		{
			var api = await libraryBook.GetApiAsync();
			var records = await api.GetRecordsAsync(libraryBook.Book.AudibleProductId);

			bookRecordEntries.Clear();
			bookRecordEntries.AddRange(records.Select(r => new BookRecordEntry(r)));
		}
		catch (Exception ex)
		{
			Serilog.Log.Error(ex, ex.Message);
			await MessageBox.Show(this, $"Libation was unable to reload records", "Reload Failed", MessageBoxButtons.OK, MessageBoxIcon.Error);
		}
		finally { await setControlEnabled(sender, true); }
	}

	#endregion

	private async Task saveRecords(IEnumerable<IRecord> records)
	{
		if (!records.Any()) return;

		try
		{
			var saveFileDialog =
			await Dispatcher.UIThread.InvokeAsync(() => new FilePickerSaveOptions
			{
				Title = "Where to export book records",
				SuggestedFileName = $"{libraryBook.Book.TitleWithSubtitle} - Records",
				DefaultExtension = "xlsx",
				ShowOverwritePrompt = true,
				FileTypeChoices = new FilePickerFileType[]
				{
					new("Excel Workbook (*.xlsx)")
					{
						Patterns = new[] { "*.xlsx" },
						AppleUniformTypeIdentifiers = new[] { "org.openxmlformats.spreadsheetml.sheet" }
					},
					new("CSV files (*.csv)")
					{
						Patterns = new[] { "*.csv" },
						AppleUniformTypeIdentifiers = new[] { "public.comma-separated-values-text" }
					},
					new("JSON files (*.json)")
					{
						Patterns = new[] { "*.json" },
						AppleUniformTypeIdentifiers  = new[] { "public.json" }
					},
					new("All files (*.*)") { Patterns = new[] { "*" } },
				}
			});

			var selectedFile = (await StorageProvider.SaveFilePickerAsync(saveFileDialog))?.TryGetLocalPath();

			if (selectedFile is null) return;

			var ext = System.IO.Path.GetExtension(selectedFile).ToLowerInvariant();

			switch (ext)
			{
				case ".xlsx":
				default:
					await Task.Run(() => RecordExporter.ToXlsx(selectedFile, records));
					break;
				case ".csv":
					await Task.Run(() => RecordExporter.ToCsv(selectedFile, records));
					break;
				case ".json":
					await Task.Run(() => RecordExporter.ToJson(selectedFile, libraryBook, records));
					break;
			}
		}
		catch (Exception ex)
		{
			await MessageBox.ShowAdminAlert(this, "Error attempting to export your library.", "Error exporting", ex);
		}
	}

	#region DataGrid Bindings

	public class BookRecordEntry : ViewModels.ViewModelBase
	{
		private const string DateFormat = "yyyy-MM-dd HH\\:mm";
		public IRecord Record { get; }
		public bool IsChecked { get => field; set => this.RaiseAndSetIfChanged(ref field, value); }
		public string Type => Record.GetType().Name;
		public string Start => formatTimeSpan(Record.Start);
		public string Created => Record.Created.ToString(DateFormat);
		public string Modified => Record is IAnnotation annotation ? annotation.Created.ToString(DateFormat) : string.Empty;
		public string End => Record is IRangeAnnotation range ? formatTimeSpan(range.End) : string.Empty;
		public string Note => (Record as IRangeAnnotation)?.Text ?? string.Empty;
		public string Title => (Record as Clip)?.Title ?? string.Empty;
		public BookRecordEntry(IRecord record) => Record = record;

		private static string formatTimeSpan(TimeSpan timeSpan)
		{
			int h = (int)timeSpan.TotalHours;
			int m = timeSpan.Minutes;
			int s = timeSpan.Seconds;
			int ms = timeSpan.Milliseconds;

			return ms == 0 ? $"{h:d2}:{m:d2}:{s:d2}" : $"{h:d2}:{m:d2}:{s:d2}.{ms:d3}";
		}
	}

	#endregion
}
