using System.Text.RegularExpressions;
using Core.Entities;
using FuzzySharp;

namespace Core.Parsers;

public class EventOccurrenceParser 
{
	private FileSystemInfo _txtFile;
	private ICollection<EventDefinition> _events;

	public EventOccurrenceParser(FileSystemInfo txtFile, ICollection<EventDefinition> events)
	{
		_events = events;
		_txtFile = txtFile;
	}

	private Regex _re =
		new (
				@"" + // 
				@"(?<Name>^[^#].*)\s" +
				@"(?<Month>February|March|April|May|June|July)\s" +
				@"(?<DayOfMonth>\d{1,2});?\s" +
				@"(?<TimeAndLocation>.*)"
			);

	private readonly Regex _timeRe = new(@"(?<Hour>\d{1,2}):?(?<Minute>\d{2})?\s?(?<APM>(?:a|p)\.?m\.?)");

	private readonly Regex _timeLocationRegex = new(@"(?<Time>.*(?>[AaPp]\.?[Mm]\.?))(?<Location>[\s\t].*)?");

	public IDictionary<EventDefinition, List<EventOccurrence>> Parse()
    {
        var occurrences = new Dictionary<EventDefinition, List<EventOccurrence>>();
        EventDefinition currentEventDefinition = null;

		var lines = File.ReadLines(_txtFile.FullName);
        foreach (var line in lines)
        {
			var match = _re.Match(line);
			if (!match.Success)
			{
				if (line.Contains("MS"))
				{
					var evt =
						(from e in _events
							let rat = Fuzz.Ratio(e.Name, line.Trim())
							where rat > 50
							orderby rat descending
							select e).FirstOrDefault();
					if (evt == null)
						continue;
					currentEventDefinition = evt;
					continue;
				}
				if (line == "General Schedule")
				{
					currentEventDefinition = EventDefinition.GeneralSchedule;
					continue;
				}

				if (line == "Voting Delegates")
				{
					currentEventDefinition = EventDefinition.VotingDelegates;
					continue;
				}

				continue;
			}

			if (currentEventDefinition == null)
				continue;
			 
			var occurrenceName = match.Groups["Name"].Captures[0].Value;
			var month = match.Groups["Month"].Captures[0].Value;
			var dayOfMonth = match.Groups["DayOfMonth"].Captures[0].Value;
			var timeAndLocation = match.Groups["TimeAndLocation"].Captures[0].Value;


			occurrenceName = Regex.Replace(occurrenceName,
				@"(?<Weekday>Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday),\s?$", "").Trim();


			timeAndLocation = SanitizeInput(timeAndLocation);
			var timeAndLocationMatch = _timeLocationRegex.Match(timeAndLocation);

			var time = timeAndLocation;
			var location = string.Empty;

			if (timeAndLocationMatch.Success)
			{
				time= timeAndLocationMatch.Groups["Time"].Captures[0].Value;
				if (timeAndLocationMatch.Groups["Location"].Success)
					location = timeAndLocationMatch.Groups["Location"].Captures[0].Value;
			}

			var startDate = ParseDate(month, dayOfMonth, DateTime.Now.Year);
			var startTime = ParseStartTime(time);
			var t = new DateTime(startDate, startTime);

			var eventOccurrence = new EventOccurrence
			{
				Name = occurrenceName, StartTime = t, Time = $"{time}", Date = $"{month} {dayOfMonth}",
				Location = location
			};

			if (!occurrences.ContainsKey(currentEventDefinition))
				occurrences.Add(currentEventDefinition, []);
			occurrences[currentEventDefinition].Add(eventOccurrence);
        }

        return occurrences;
    }

    private string SanitizeInput(string input)
    {

	    input = input.Replace("–", "-");
	    input = input.Replace("—", "-");

	    return input;
    }

    private DateOnly ParseDate(string month, string dayOfMonth, int year)
    {
	    int monthNum = 1;
	    switch (month)
	    {
		    case "February":
			    monthNum = 2;
			    break;
		    case "March":
			    monthNum = 3;
			    break;
		    case "April":
			    monthNum = 4;
			    break;
		    case "May":
			    monthNum = 5;
			    break;
		    case "June":
			    monthNum = 6;
			    break;
		    case "July":
			    monthNum = 7;
			    break;
	    }

	    var day = int.Parse(dayOfMonth);
		return new DateOnly(year, monthNum, day); ;
    }

    private TimeOnly ParseStartTime(string time)
    {
	    int hour = 0;
	    int minute = 0;

		// get the part of the time before a timespan
	    if (time.Contains(" - "))
	    {
		    time = time[..time.IndexOf(" - ", StringComparison.Ordinal)];

	    }

	    if (time == "NOON")
		    hour = 12;
	    else
	    {
		    var timeMatch = _timeRe.Match(time.ToLower());
		    if (timeMatch.Success)
		    {
			    hour = int.Parse(timeMatch.Groups["Hour"].Captures[0].Value);
			    if (timeMatch.Groups["Minute"].Success)
			    {
				    minute = int.Parse(timeMatch.Groups["Minute"].Captures[0].Value);
				}

			    if (timeMatch.Groups["APM"].Captures[0].Value is "p.m." or "pm" && hour < 12)
				    hour += 12;
		    }
	    }

	    return new TimeOnly(hour, minute, 0);
    }
}