using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEditor;
using UnityEngine;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
using SingularityGroup.HotReload.Editor.Localization;
using Application = UnityEngine.Application;
#if UNITY_2021_3_OR_NEWER
using System.IO;
using System.IO.Compression;
using SingularityGroup.HotReload.Editor.Cli;
using SingularityGroup.HotReload.EditorDependencies;
using CompressionLevel = System.IO.Compression.CompressionLevel;
#endif

namespace SingularityGroup.HotReload.Editor {

	internal enum ReportMode {
		BugReport,
		Feedback
	}

	internal class HotReloadBugReportWindow : EditorWindow {
		static List<string> SeverityChoices => new List<string> {
			Translations.BugReport.SeverityNormal,
			Translations.BugReport.SeverityLow,
			Translations.BugReport.SeverityHigh,
		};

		static List<string> FrequencyChoices => new List<string> {
			Translations.BugReport.FrequencyFirstTime,
			Translations.BugReport.FrequencySometimes,
			Translations.BugReport.FrequencyAlways,
		};

		[SerializeField] ReportMode _mode = ReportMode.BugReport;

		[SerializeField] VisualTreeAsset visualTree;
		[SerializeField] StyleSheet styleSheet;
		
		string _discordUrl;
		string _contactUrl;

		TextField _titleField;
		PopupField<string> _severityPopup;
		PopupField<string> _frequencyPopup;
		TextField _emailField;
		IMGUIContainer _detailsContainer;
		[SerializeField] string _detailsText = "";
		[SerializeField] string _emailText = "";
		[SerializeField] string _titleText = "";
		Label _detailsLabel;
		Label _validationLabel;
		Button _submitButton;
		VisualElement _severitySection;
		VisualElement _frequencySection;
		ScrollView _scrollView;
		VisualElement _detailsSlot;

		VisualElement _successScreen;
		VisualElement _failureScreen;
		Label _failureErrorLabel;
		Button _tabBugReport;
		Button _tabFeedback;

		bool _isOnResultScreen;

		static Vector2 GetMinSize(ReportMode mode) {
			return mode == ReportMode.Feedback
				? new Vector2(420, 500)
				: new Vector2(420, 620);
		}

		#region Public API
		/// <summary>
		/// Opens the window with the given mode and configuration.
		/// Prefer using <see cref="ReportWindowAPI"/> for a cleaner call site.
		/// </summary>
		public static HotReloadBugReportWindow Open(
			ReportMode mode,
			string discordUrl = null,
			string contactUrl = null,
			string title = null,
			string email = null,
			string details = null
		) {
			var wnd = GetWindow<HotReloadBugReportWindow>(utility: true);
			wnd._mode = mode;
			wnd._discordUrl = discordUrl;
			wnd._contactUrl = contactUrl;
			wnd.titleContent = new GUIContent(
				mode == ReportMode.Feedback
					? Translations.BugReport.WindowTitleFeedback
					: Translations.BugReport.WindowTitleBugReport);
			wnd.minSize = wnd.maxSize = GetMinSize(mode);

			wnd._titleText = title;
			wnd._emailText = email;
			wnd._detailsText = details;

			wnd.RebuildUI();
			return wnd;
		}

		/// <summary>
		/// Prefills form fields on an already-open window. Null values are ignored.
		/// </summary>
		public void Prefill() {
			if (_titleText != null && _titleField != null) {
				_titleField.SetValueWithoutNotify(_titleText);
			}
			if (_emailText != null && _emailField != null) {
				_emailField.SetValueWithoutNotify(_emailText);
			}
		}
		#endregion

		void OnEnable() {
			RebuildUI();
		}

		bool HasUnsavedFormContent() {
			if (_isOnResultScreen) {
				return false;
			}
			if (!string.IsNullOrWhiteSpace(_titleField.value)) {
				return true;
			}
			if (!string.IsNullOrWhiteSpace(_detailsText) && _detailsText != Translations.BugReport.PlaceholderDetails) {
				return true;
			}

			return false;
		}

		void OnDestroy() {
			if (_detailsContainer != null && _detailsSlot != null) {
				_detailsSlot.Remove(_detailsContainer);
				_detailsContainer = null;
			}
			if (!HasUnsavedFormContent()) {
				return;
			}

			bool discard = EditorUtility.DisplayDialog(
				Translations.BugReport.DiscardDialogTitle,
				Translations.BugReport.DiscardDialogMessage,
				Translations.BugReport.DiscardDialogConfirm,
				Translations.BugReport.DiscardDialogCancel);

			if (!discard) {
				_emailText = _emailField.value;
				_titleText = _titleField.value;
				EditorApplication.delayCall += () => {
					Open(_mode, _discordUrl, _contactUrl);
				};
			} else {
				_detailsText = null;
				_emailText = null;
				_titleText = null;
			}
		}

		void RebuildUI() {
			DetachCallbacks();
			if (visualTree == null) {
				Log.Warning($"Could not open bug report. Please reach out to our support: {_contactUrl}");
				return;
			}

			rootVisualElement.Clear();
			visualTree.CloneTree(rootVisualElement);
			if (styleSheet != null) {
				rootVisualElement.styleSheets.Add(styleSheet);
			}

			QueryElements();
			AttachIMGUIContainers();
			ApplyTranslations();
			CreatePopupFields();
			ApplyMode();
			SetupSubmit();
			SetupResultScreenButtons();
			FixSingleLineFieldAlignment(_titleField);
			FixSingleLineFieldAlignment(_emailField);
			Prefill();
			AttachCallbacks();

			ShowFormView();
		}

		void AttachIMGUIContainers() {
			_detailsSlot = rootVisualElement.Q<VisualElement>("details-field-slot");
			// Build the IMGUI textarea and inject it
			_detailsContainer = new IMGUIContainer(DrawDetailsField);
			_detailsSlot.Add(_detailsContainer);
		}

		void QueryElements() {
			_titleField = rootVisualElement.Q<TextField>("title-field");
			_emailField = rootVisualElement.Q<TextField>("email-field");
			_detailsLabel = rootVisualElement.Q<Label>("details-label");
			_validationLabel = rootVisualElement.Q<Label>("validation-label");
			_submitButton = rootVisualElement.Q<Button>("submit-button");
			_severitySection = rootVisualElement.Q<VisualElement>("severity-section");
			_frequencySection = rootVisualElement.Q<VisualElement>("frequency-section");
			_scrollView = rootVisualElement.Q<ScrollView>("scroll-view");

			_successScreen = rootVisualElement.Q<VisualElement>("success-screen");
			_failureScreen = rootVisualElement.Q<VisualElement>("failure-screen");
			_failureErrorLabel = rootVisualElement.Q<Label>("failure-error-label");
			_tabBugReport = rootVisualElement.Q<Button>("tab-bug-report");
			_tabFeedback = rootVisualElement.Q<Button>("tab-feedback");
		}

		private void DrawDetailsField() {
			// GUILayout.ExpandHeight lets it grow; clamp min so it doesn't collapse
			_detailsText = EditorGUILayout.TextArea(
				_detailsText,
				GUILayout.ExpandWidth(true),
				GUILayout.MinHeight(160)
			);
		}

		void ApplyTranslations() {
			// Form labels
			rootVisualElement.Q<Label>(className: "section-label") // Title section — first one
				?.SetText(Translations.BugReport.LabelTitle);
			rootVisualElement.Q("severity-section")?.Q<Label>(className: "section-label")
				?.SetText(Translations.BugReport.LabelIssueSeverity);
			rootVisualElement.Q("frequency-section")?.Q<Label>(className: "section-label")
				?.SetText(Translations.BugReport.LabelHowOften);
			rootVisualElement.Q("tabs")?.Q<Button>("tab-bug-report")
				?.SetText(Translations.BugReport.TabBugReport);
			rootVisualElement.Q("tabs")?.Q<Button>("tab-feedback")
				?.SetText(Translations.BugReport.TabFeedback);

			// Contact section — scoped by class since it has no name
			var contactSection = rootVisualElement.Q(className: "contact-section");
			contactSection?.Q<Label>(className: "section-label")?.SetText(Translations.BugReport.LabelContactEmail);
			contactSection?.Q<Label>(className: "fine-print")?.SetText(Translations.BugReport.LabelContactEmailFinePrint);

			// Submit button
			_submitButton.text = Translations.BugReport.ButtonSubmit;

			// Success screen
			var successContent = _successScreen?.Q(className: "result-content");
			successContent?.Q<Label>(className: "result-title")?.SetText(Translations.BugReport.SuccessTitle);
			successContent?.Q<Label>(className: "result-message")?.SetText(Translations.BugReport.SuccessMessage);
			_successScreen?.Q<Button>("success-discord-button")?.SetText(Translations.BugReport.ButtonJoinDiscord);
			

			// Failure screen
			var failureContent = _failureScreen?.Q(className: "result-content");
			failureContent?.Q<Label>(className: "result-title")?.SetText(Translations.BugReport.FailureTitle);
			// result-message--error is the dynamic error label, skip it; translate the static one
			var failureMessages = failureContent?.Query<Label>(className: "result-message").ToList();
			if (failureMessages != null) {
				foreach (var lbl in failureMessages) {
					if (!lbl.ClassListContains("result-message--error")) {
						lbl.text = Translations.BugReport.FailureMessage;
					}
				}
			}
			_failureScreen?.Q<Button>("failure-discord-button")?.SetText(Translations.BugReport.ButtonJoinDiscord);
			_failureScreen?.Q<Button>("failure-contact-button")?.SetText(Translations.BugReport.ButtonContactUs);
		}

		#region View switching
		void ShowFormView() {
			_isOnResultScreen = false;
			_scrollView.style.display = DisplayStyle.Flex;
			_successScreen.RemoveFromClassList("result-screen--visible");
			_failureScreen.RemoveFromClassList("result-screen--visible");
		}

		void ShowSuccessScreen() {
			_isOnResultScreen = true;
			_scrollView.style.display = DisplayStyle.None;
			_tabFeedback.style.display = DisplayStyle.None;
			_tabBugReport.style.display = DisplayStyle.None;
			_failureScreen.RemoveFromClassList("result-screen--visible");
			_successScreen.AddToClassList("result-screen--visible");
		}

		void ShowFailureScreen(string errorMessage) {
			_scrollView.style.display = DisplayStyle.None;
			_tabFeedback.style.display = DisplayStyle.None;
			_tabBugReport.style.display = DisplayStyle.None;
			_successScreen.RemoveFromClassList("result-screen--visible");

			if (_failureErrorLabel != null) {
				if (string.IsNullOrEmpty(errorMessage)) {
					_failureErrorLabel.style.display = DisplayStyle.None;
				} else {
					_failureErrorLabel.text = errorMessage;
					_failureErrorLabel.style.display = DisplayStyle.Flex;
				}
			}

			_failureScreen.AddToClassList("result-screen--visible");
			_isOnResultScreen = true;
		}
		#endregion

		void ApplyMode() {
			bool isBugReport = _mode == ReportMode.BugReport;
			this.minSize = this.maxSize = GetMinSize(_mode);

			if (isBugReport) {
				_severitySection.style.display = DisplayStyle.Flex;
				_frequencySection.style.display = DisplayStyle.Flex;
				_severityPopup.style.display = DisplayStyle.Flex;
				_frequencyPopup.style.display = DisplayStyle.Flex;
				_detailsLabel.text = Translations.BugReport.LabelDetails;
				_tabBugReport.AddToClassList("tab--active");
				_tabFeedback.RemoveFromClassList("tab--active");
				if (string.IsNullOrEmpty(_detailsText)) {
				   _detailsText = Translations.BugReport.PlaceholderDetails;
				   _detailsContainer.MarkDirtyRepaint();
				}
			} else {
				_severitySection.style.display = DisplayStyle.None;
				_frequencySection.style.display = DisplayStyle.None;
				_severityPopup.style.display = DisplayStyle.None;
				_frequencyPopup.style.display = DisplayStyle.None;
				_detailsLabel.text = Translations.BugReport.LabelFeedback;
				_tabFeedback.AddToClassList("tab--active");
				_tabBugReport.RemoveFromClassList("tab--active");
				if (_detailsText == Translations.BugReport.PlaceholderDetails) {
				   _detailsText = string.Empty;
				   _detailsContainer.MarkDirtyRepaint();
				}
			}
		}

		void CreatePopupFields() {
			var severityContainer = rootVisualElement.Q<VisualElement>("severity-container");
			_severityPopup = new PopupField<string>(
				string.Empty,
				SeverityChoices,
				0);
			_severityPopup.AddToClassList("input-field");
			severityContainer.Add(_severityPopup);

			var frequencyContainer = rootVisualElement.Q<VisualElement>("frequency-container");
			_frequencyPopup = new PopupField<string>(
				string.Empty,
				FrequencyChoices,
				0);
			_frequencyPopup.AddToClassList("input-field");
			frequencyContainer.Add(_frequencyPopup);
		}

		void SelectBugReport() {
			_mode = ReportMode.BugReport;
			ApplyMode();
		}

		void SelectFeedback() {
			_mode = ReportMode.Feedback;
			ApplyMode();
		}
		
		void AttachCallbacks() {
			if (_tabBugReport != null) {
				_tabBugReport.clicked += SelectBugReport;
			}
			if (_tabFeedback != null) {
				_tabFeedback.clicked += SelectFeedback;
			}
			if (_emailField != null) {
				_emailField.RegisterValueChangedCallback(SetEmailText);
			}
			if (_titleField != null) {
				_titleField.RegisterValueChangedCallback(SetTitleText);
			}
		}

		void DetachCallbacks() {
			if (_tabBugReport != null) {
				_tabBugReport.clicked -= SelectBugReport;
			}
			if (_tabFeedback != null) {
				_tabFeedback.clicked -= SelectFeedback;
			}
			if (_emailField != null) {
				_emailField.UnregisterValueChangedCallback(SetEmailText);
			}
			if (_titleField != null) {
				_titleField.UnregisterValueChangedCallback(SetTitleText);
			}
		}

		void SetEmailText(ChangeEvent<string> @event) {
			_emailText = @event.newValue;
		}
		
		void SetTitleText(ChangeEvent<string> @event) {
			_titleText = @event.newValue;
		}

		static void FixSingleLineFieldAlignment(TextField field) {
			field.RegisterCallback<GeometryChangedEvent>(evt => {
				var input = field.Q(className: "unity-text-field__input");
				if (input != null) {
					input.style.alignItems = Align.Center;
				}
			});
		}

		#region Validation & submit
		void SetupSubmit() {
			_submitButton.clickable.clicked += OnSubmitClicked;
		}

		async void OnSubmitClicked() {
			HideValidation();

			if (string.IsNullOrWhiteSpace(_titleField.value)) {
				ShowValidation(Translations.BugReport.ValidationTitleRequired);
				return;
			}

			var details = _detailsText;

			var report = new Report {
				Mode = _mode,
				Id = Guid.NewGuid().ToString("N").Substring(0, 10),
				Title = _titleField.value.Trim(),
				Severity = _severityPopup != null ? _severityPopup.value : null,
				Frequency = _frequencyPopup != null ? _frequencyPopup.value : null,
				ContactEmail = _emailField.value != null ? _emailField.value.Trim() : null,
				Details = details != null ? details.Trim() : null
			};

			_submitButton.SetEnabled(false);
			_submitButton.text = Translations.BugReport.ButtonSubmitting;

			string error;
			try {
				error = await HandleBugReport(report);
			} catch (Exception ex) {
				error = string.Format(Translations.BugReport.SubmitHandlerError, ex.Message);
			}

			if (this == null) {
				return;
			}

			_submitButton.SetEnabled(true);
			_submitButton.text = Translations.BugReport.ButtonSubmit;

			if (error == null) {
				ShowSuccessScreen();
				if (report.Mode == ReportMode.BugReport) {
					// Back up log and patches locally for this bug report
					// They can be later used to simplify reproducing and fixing the issue, but due to our privacy policy they can't be auto uploaded so we need to ask for them
					#if UNITY_2021_3_OR_NEWER
					var logName = LogsHelper.FindRecentLog(HotReloadAboutTab.logsPath);
					try {
						CreateBugReportZip(
							Path.Combine(CliUtils.GetCliTempDir(), "Backup"), 
							logName == null ? null : Path.Combine(HotReloadAboutTab.logsPath, logName), 
							Path.Combine(PackageConst.LibraryCachePath, "BugReports", $"{report.Id}.zip")
						);
					} catch {
						// Fail silently. If zip is not available we will have to ask for a reproduce
					}
					#endif
				}
			} else {
				ShowFailureScreen(error);
			}
		}
		
		private static Task<string> HandleBugReport(Report report) {
			var hwId = HotReloadPrefs.HardwareId;
			if (string.IsNullOrEmpty(hwId)) {
				hwId = "unknown";
			}
			return RequestHelper.SubmitBugReport(new BugReport {
				reportId = report.Id,
				label = report.Mode == ReportMode.Feedback ? "Feedback" : "Bug Report",
				title = report.Title,
				description = report.Details,
				email = report.ContactEmail,
				hwId = hwId,
				hotReloadVersion = PackageConst.Version,
				unityVersion = Application.unityVersion,
				operatingSystemVersionInfo = SystemInfo.operatingSystem,
			});
		}
		
		#if UNITY_2021_3_OR_NEWER
		private static void CreateBugReportZip(
			string sourceDir,
			string sourceFile,
			string outputZipPath
		) {
			var files = Directory.GetFiles(sourceDir, "*", SearchOption.AllDirectories);
			if (files.Length == 0 && sourceFile == null) {
				return;
			}
            Directory.CreateDirectory(new FileInfo(outputZipPath).DirectoryName!);

			using (var stream = new FileStream(outputZipPath, FileMode.Create, FileAccess.Write, FileShare.None, 65536)) {
				using (var archive = new ZipArchive(stream, ZipArchiveMode.Create, leaveOpen: false)) {
					if (sourceFile != null) {
						 AddFileToArchive(archive, sourceFile, Path.GetFileName(sourceFile));
					}
					foreach (string filePath in Directory.GetFiles(sourceDir, "*", SearchOption.AllDirectories)) {
						string relativePath = Path.GetRelativePath(sourceDir, filePath);
						string entryName = $"Patches/{relativePath}".Replace('\\', '/');
						AddFileToArchive(archive, filePath, entryName);
					}
				}
			}
		}

		private static void AddFileToArchive(ZipArchive archive, string filePath, string entryName) {
			var entry = archive.CreateEntry(entryName, CompressionLevel.Optimal);
			using (var entryStream = entry.Open()) {
				using (var fileStream = new FileStream(
					       filePath,
					       FileMode.Open,
					       FileAccess.Read,
					       FileShare.ReadWrite, // allow concurrent writers
					       65536)) {
					fileStream.CopyTo(entryStream);
				}
			}
		}
		#endif
		
		void ShowValidation(string message) {
			_validationLabel.text = message;
			_validationLabel.AddToClassList("validation-label--visible");
		}

		void HideValidation() {
			_validationLabel.RemoveFromClassList("validation-label--visible");
		}
		#endregion

		#region Result screen buttons
		void SetupResultScreenButtons() {
			var successDiscord = rootVisualElement.Q<Button>("success-discord-button");
			var failureDiscord = rootVisualElement.Q<Button>("failure-discord-button");
			var failureContact = rootVisualElement.Q<Button>("failure-contact-button");

			if (successDiscord != null) {
				successDiscord.clickable.clicked += OnDiscordClicked;
			}

			if (failureDiscord != null) {
				failureDiscord.clickable.clicked += OnDiscordClicked;
			}
			if (failureContact != null) {
				failureContact.clickable.clicked += OnContactClicked;
			}

			bool hasDiscord = !string.IsNullOrEmpty(_discordUrl);
			bool hasContact = !string.IsNullOrEmpty(_contactUrl);

			if (successDiscord != null) {
				successDiscord.style.display = hasDiscord ? DisplayStyle.Flex : DisplayStyle.None;
			}
			if (failureDiscord != null) {
				failureDiscord.style.display = hasDiscord ? DisplayStyle.Flex : DisplayStyle.None;
			}
			if (failureContact != null) {
				failureContact.style.display = hasContact ? DisplayStyle.Flex : DisplayStyle.None;
			}
		}

		void OnDiscordClicked() {
			if (!string.IsNullOrEmpty(_discordUrl)) {
				Application.OpenURL(_discordUrl);
			}
		}

		void OnContactClicked() {
			if (!string.IsNullOrEmpty(_contactUrl)) {
				Application.OpenURL(_contactUrl);
			}
		}
		#endregion
	}

	[Serializable]
	internal class Report {
		public string Id;
		public ReportMode Mode;
		public string Title;
		public string Severity;
		public string Frequency;
		public string ContactEmail;
		public string Details;
	}

}

// Small extension to avoid repetitive null checks on label/button text assignment
namespace SingularityGroup.HotReload.Editor {
	internal static class VisualElementExtensions {
		public static void SetText(this UnityEngine.UIElements.Label label, string text) {
			if (label != null) label.text = text;
		}
		public static void SetText(this UnityEngine.UIElements.Button button, string text) {
			if (button != null) button.text = text;
		}
	}
}
