using Cryville.EEW.Core;
using Cryville.EEW.CWAOpenData;
using Cryville.EEW.CWAOpenData.Model;
using Cryville.EEW.CWAOpenData.TTS;
using Cryville.EEW.GlobalQuake;
using Cryville.EEW.JMAAtom;
using Cryville.EEW.JMAAtom.TTS;
using Cryville.EEW.NOAA;
using Cryville.EEW.NOAA.TTS;
using Cryville.EEW.Report;
using Cryville.EEW.Unity.Map;
using Cryville.EEW.Unity.UI;
using Cryville.EEW.UpdateChecker;
using Cryville.EEW.Wolfx;
using Cryville.EEW.Wolfx.Model;
using Cryville.EEW.Wolfx.TTS;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

namespace Cryville.EEW.Unity {
	sealed class Worker : MonoBehaviour {
		public static Worker Instance { get; private set; }

		[SerializeField] CameraController m_cameraController;
		[SerializeField] MapElementManager m_mapElementManager;
		[SerializeField] EventOngoingListView m_ongoingEventList;
		[SerializeField] EventGroupListView m_historyEventGroupList;
		[SerializeField] GameObject m_connectingHint;

		GroupingCoreWorker _worker;
		CancellationTokenSource _cancellationTokenSource;

		void Awake() {
			if (Instance != null) {
				Destroy(this);
				throw new InvalidOperationException("Duplicate worker.");
			}
			Instance = this;

			App.Init();

			_worker = new(new TTSWorker());
			_cancellationTokenSource = new();
		}

		void Start() {
			LocalizedResources.Init(new LocalizedResourcesManager());
			RegisterViewModelGenerators(_worker);
			RegisterTTSMessageGenerators(_worker);
			BuildWorkers();
			_worker.RVMGeneratorContext = SharedSettings.Instance;
			_worker.TTSMessageGeneratorContext = SharedSettings.Instance;
			_ongoingReportManager.Changed += OnOngoingReported;
			_worker.Reported += OnReported;
			_worker.GroupUpdated += OnGroupUpdated;
			_worker.GroupRemoved += OnGroupRemoved;
			Task.Run(() => GatewayVerify(_cancellationTokenSource.Token)).ContinueWith(task => {
				if (task.IsFaulted) {
					OnReported(this, new() { Title = task.Exception.Message });
					return;
				}
				_verified = true;
				_uiActionQueue.Enqueue(() => m_connectingHint.SetActive(false));
				Task.Run(() => ScheduledGatewayVerify(_cancellationTokenSource, _cancellationTokenSource.Token));
				Task.Run(() => _worker.RunAsync(_cancellationTokenSource.Token));
				Task.Run(() => _ongoingReportManager.RunAsync(_cancellationTokenSource.Token));
			}, TaskScheduler.Current);
		}

		void OnDestroy() {
			_cancellationTokenSource.Cancel();
			_worker.Dispose();
			_ongoingReportManager.Dispose();
		}

		static void RegisterViewModelGenerators(CoreWorker worker) {
			worker.RegisterViewModelGenerator(new CENCEarthquakeRVMGenerator());
			worker.RegisterViewModelGenerator(new CENCEEWRVMGenerator());
			worker.RegisterViewModelGenerator(new CWAEarthquakeRVMGenerator());
			worker.RegisterViewModelGenerator(new CWAEEWRVMGenerator());
			worker.RegisterViewModelGenerator(new CWATsunamiRVMGenerator());
			worker.RegisterViewModelGenerator(new FujianEEWRVMGenerator());
			worker.RegisterViewModelGenerator(new GlobalQuakeRVMGenerator());
			worker.RegisterViewModelGenerator(new JMAAtomRVMGenerator());
			worker.RegisterViewModelGenerator(new JMAEEWRVMGenerator());
			worker.RegisterViewModelGenerator(new NOAAAtomRVMGenerator());
			worker.RegisterViewModelGenerator(new SichuanEEWRVMGenerator());
			worker.RegisterViewModelGenerator(new VersionRVMGenerator());
		}
		static void RegisterTTSMessageGenerators(CoreWorker worker) {
			worker.RegisterTTSMessageGenerator(new CENCEarthquakeTTSMessageGenerator());
			worker.RegisterTTSMessageGenerator(new CENCEEWTTSMessageGenerator());
			worker.RegisterTTSMessageGenerator(new CWAEarthquakeTTSMessageGenerator());
			worker.RegisterTTSMessageGenerator(new CWAEEWTTSMessageGenerator());
			worker.RegisterTTSMessageGenerator(new CWATsunamiTTSMessageGenerator());
			worker.RegisterTTSMessageGenerator(new FujianEEWTTSMessageGenerator());
			worker.RegisterTTSMessageGenerator(new JMAAtomTTSMessageGenerator());
			worker.RegisterTTSMessageGenerator(new JMAEEWTTSMessageGenerator());
			worker.RegisterTTSMessageGenerator(new NOAATTSMessageGenerator());
			worker.RegisterTTSMessageGenerator(new SichuanEEWTTSMessageGenerator());
		}

		bool _verified;
		void BuildWorkers() {
#if UNITY_EDITOR
			_worker.AddWorker(new WolfxWorker(new Uri("ws://localhost:9995/wolfx")));
			_worker.AddWorker(new JMAAtomWorker(new Uri("http://localhost:9095/eqvol.xml")));
			_worker.AddWorker(new CWAReportWorker<Tsunami>(new Uri("http://localhost:9095/E-A0014-001.json"), "1"));
			_worker.AddWorker(new CWAReportWorker<Earthquake>(new Uri("http://localhost:9095/E-A0015-001.json"), "1"));
			_worker.AddWorker(new CWAReportWorker<Earthquake>(new Uri("http://localhost:9095/E-A0016-001.json"), "1"));
			_worker.AddWorker(new NOAAAtomWorker(new("http://localhost:9095/PAAQAtom.xml")));
			_worker.AddWorker(new UpdateCheckerWorker(typeof(Worker).Assembly.GetName().Version?.ToString(3) ?? "", "unity"));
#else
			foreach (var source in SharedSettings.Instance.EventSources) {
				_worker.AddWorker(source switch {
					CWAOpenDataEventSourceConfig cwaOpenData => cwaOpenData.Subtype switch {
						"E-A0014-001" => new CWAReportWorker<Tsunami>(new Uri("https://opendata.cwa.gov.tw/api/v1/rest/datastore/E-A0014-001"), cwaOpenData.Token, 1440, 17280),
						"E-A0015-001" => new CWAReportWorker<Earthquake>(new Uri("https://opendata.cwa.gov.tw/api/v1/rest/datastore/E-A0015-001"), cwaOpenData.Token),
						"E-A0016-001" => new CWAReportWorker<Earthquake>(new Uri("https://opendata.cwa.gov.tw/api/v1/rest/datastore/E-A0016-001"), cwaOpenData.Token),
						_ => throw new InvalidOperationException("Unknown CWA open data sub-type."),
					},
					GlobalQuakeServer15EventSourceConfig gq => new GlobalQuakeWorker15(gq.Host, gq.Port),
					GlobalQuakeServerEventSourceConfig gq => new GlobalQuakeWorker(gq.Host, gq.Port),
					JMAAtomEventSourceConfig jmaAtom => BuildJMAAtomWorkerFilter(new JMAAtomWorker(new("https://www.data.jma.go.jp/developer/xml/feed/eqvol.xml")), jmaAtom),
					NOAAEventSourceConfig noaaAtom => noaaAtom.Subtype switch {
						"PAAQ" => new NOAAAtomWorker(new("https://www.tsunami.gov/events/xml/PAAQAtom.xml"), new("https://www.tsunami.gov/"), new("/php/esri.php?e=t", UriKind.Relative), "PAAQ"),
						"PHEB" => new NOAAAtomWorker(new("https://www.tsunami.gov/events/xml/PHEBAtom.xml"), new("https://www.tsunami.gov/"), new("/php/esri.php?e=t", UriKind.Relative), "PHEB"),
						_ => throw new InvalidOperationException("Unknown NOAA sub-type."),
					},
					UpdateCheckerEventSourceConfig => new UpdateCheckerWorker(typeof(Worker).Assembly.GetName().Version?.ToString(3) ?? "", "unity"),
					WolfxEventSourceConfig wolfx => BuildWolfxWorkerFilter(new WolfxWorker(new Uri("wss://ws-api.wolfx.jp/all_eew")), wolfx),
					_ => throw new InvalidOperationException("Unknown event source type."),
				});
			}
#endif
		}
		JMAAtomWorker BuildJMAAtomWorkerFilter(JMAAtomWorker worker, JMAAtomEventSourceConfig config) {
			if (config.Filter != null) worker.SetFilter(config.Filter);
			worker.IsFilterWhitelist = config.IsFilterWhitelist;
			return worker;
		}
		WolfxWorker BuildWolfxWorkerFilter(WolfxWorker worker, WolfxEventSourceConfig config) {
			if (config.Filter != null) worker.SetFilter(config.Filter.Select(i => i switch {
				"cenc_eew" => typeof(CENCEEW),
				"cenc_eqlist" => typeof(WolfxEarthquakeList<CENCEarthquake>),
				"cwa_eew" => typeof(CWAEEW),
				"fj_eew" => typeof(FujianEEW),
				"jma_eew" => typeof(JMAEEW),
				"sc_eew" => typeof(SichuanEEW),
				_ => throw new InvalidOperationException("Unknown Wolfx event type."),
			}));
			worker.IsFilterWhitelist = config.IsFilterWhitelist;
			return worker;
		}

		readonly OngoingReportManager _ongoingReportManager = new();
		readonly ConcurrentQueue<Action> _uiActionQueue = new();
		ReportViewModel _latestHistoryReport;
		void OnReported(object sender, ReportViewModel e) {
			if (e.Model is Exception && e.Model is not SourceWorkerNetworkException)
				Debug.LogError(e);
			_ongoingReportManager.Report(e);
			_uiActionQueue.Enqueue(() => {
				m_mapElementManager.SetSelected(e);
				if (e.InvalidatedTime == null && (!(e.RevisionKey?.IsCancellation ?? false))) {
					_latestHistoryReport = e;
				}
				m_cameraController.OnMapElementUpdated();
			});
		}
		void OnOngoingReported(ReportViewModel item, CollectionChangeAction action) {
			if (action == CollectionChangeAction.Add) {
				_uiActionQueue.Enqueue(() => {
					if (m_mapElementManager.Add(item)) {
						m_mapElementManager.SetSelected(null);
					}
					m_ongoingEventList.Add(item);
					m_cameraController.OnMapElementUpdated();
				});
			}
			else if (action == CollectionChangeAction.Remove) {
				_uiActionQueue.Enqueue(() => {
					m_mapElementManager.Remove(item);
					if (m_mapElementManager.Count == 0 && SharedSettings.Instance.DoSwitchBackToHistory && _latestHistoryReport is not null) {
						m_mapElementManager.SetSelected(_latestHistoryReport, true);
					}
					m_ongoingEventList.Remove(item);
					m_cameraController.OnMapElementUpdated();
				});
			}
		}
		void OnGroupUpdated(object sender, ReportGroup e) {
			_uiActionQueue.Enqueue(() => m_historyEventGroupList.UpdateGroup(e));
		}
		void OnGroupRemoved(object sender, ReportGroup e) {
			_uiActionQueue.Enqueue(() => m_historyEventGroupList.RemoveGroup(e));
		}
		public void SetSelected(ReportViewModel e) {
			m_mapElementManager.SetSelected(e);
			m_cameraController.OnMapElementUpdated();
		}
		public void SetCurrent(ReportViewModel e) {
			if (m_mapElementManager.SetCurrent(e)) {
				m_cameraController.OnMapElementUpdated();
			}
		}

		void Update() {
			while (_uiActionQueue.TryDequeue(out var action)) {
				action();
			}
		}

		async Task ScheduledGatewayVerify(CancellationTokenSource source, CancellationToken cancellationToken) {
			Exception lastEx = null;
			for (int i = 0; i < 8; i++) {
				await Task.Delay(TimeSpan.FromHours(3), cancellationToken).ConfigureAwait(true);
				try {
					await GatewayVerify(cancellationToken).ConfigureAwait(true);
					i = -1;
				}
				catch (HttpRequestException ex) {
					lastEx = ex;
				}
				catch (WebException ex) {
					lastEx = ex;
				}
				catch (InvalidOperationException ex) {
					lastEx = ex;
					break;
				}
			}
			if (lastEx != null) {
				OnReported(this, new() { Title = lastEx.Message });
				_verified = false;
			}
			source.Cancel();
		}
#if UNITY_EDITOR
#pragma warning disable CS1998
#endif
		static async Task GatewayVerify(CancellationToken cancellationToken) {
#if !UNITY_EDITOR
			using var client = new HttpClient();
			client.DefaultRequestHeaders.UserAgent.ParseAdd(SharedSettings.Instance.UnityUserAgent);
			using var response = await client.GetAsync(new Uri("https://gateway.cryville.world/?rin=" + SharedSettings.Instance.Id), cancellationToken).ConfigureAwait(true);
			if (response.StatusCode is >= ((HttpStatusCode)400) and < ((HttpStatusCode)500)) {
				throw new InvalidOperationException(response.ReasonPhrase);
			}
			response.EnsureSuccessStatusCode();
#endif
		}
#if UNITY_EDITOR
#pragma warning restore CS1998
#endif
	}
}
