using System.Text.Json;

namespace View_by_Distance.Shared.Models.Stateless.Methods;

internal abstract class Person
{

    // ...

    private static List<string> ValidatePerson(Properties.IStorage storage, Models.PersonId id, Models.PersonBirthday birthday, Models.PersonName name)
    {
        List<string> results = new();
        if (birthday is null)
            throw new Exception("Birthday must be supplied!");
        if (birthday.Value > DateTime.Now)
            results.Add("Birthday must be in the past!");
        if (id is null)
            throw new Exception("Birthday must be supplied!");
        if (id.Value != birthday.Value.Ticks)
            results.Add("Id must be Birthday ticks!");
        if (name.First is null || string.IsNullOrEmpty(name.First.Value))
            results.Add("Fist Name must be supplied!");
        if (PersonBirthday.DoesBirthDateExits(storage, birthday))
            results.Add("BirthDate already exits!");
        return results;
    }

    internal static Models.Person CreatePerson(Properties.IStorage storage, Models.PersonBirthday birthday, Models.PersonName name, List<Models.PersonComment> comments, List<Models.PersonURL> urls, List<Models.PersonNumber> numbers, List<Models.PersonEmail> emails, List<Models.PersonAddress> addresses)
    {
        Models.Person result;
        Models.PersonId id = new(birthday.Value.Ticks);
        if (birthday.Value == DateTime.MinValue)
            birthday = PersonBirthday.GetNextBirthDate(storage);
        List<string> results = ValidatePerson(storage, id, birthday, name);
        if (results.Any())
            throw new Exception(string.Join(Environment.NewLine, results));
        if (comments is null)
            comments = new();
        if (urls is null)
            urls = new();
        if (numbers is null)
            numbers = new();
        if (emails is null)
            emails = new();
        if (addresses is null)
            addresses = new();
        result = new(id, birthday, name, comments, urls, numbers, emails, addresses);
        return result;
    }

    internal static Dictionary<DateTime, string[]> Split(string knownPeopleFile)
    {
        Dictionary<DateTime, string[]> results = new();
        string[] segments;
        DateTime personKey;
        List<string> temporarySegments;
        const string KeyFormat = "yyyy-MM-dd_HH";
        DateTime incrementDate = new(1500, 1, 1);
        string[] lines = File.ReadAllLines(knownPeopleFile);
        _ = incrementDate.AddDays(lines.Length);
        System.Globalization.CultureInfo cultureInfo = System.Globalization.CultureInfo.InvariantCulture;
        foreach (string line in lines)
        {
            if (string.IsNullOrEmpty(line))
                continue;
            segments = line.Replace(" //", "\t//").Split('\t');
            if (segments.Length < 1)
                continue;
            if (segments[0].Length != KeyFormat.Length || !segments[0].Contains('-') || !segments[0].Contains('_'))
            {
                temporarySegments = segments.ToList();
                temporarySegments.Insert(0, incrementDate.ToString(KeyFormat));
                segments = temporarySegments.ToArray();
                incrementDate = incrementDate.AddDays(1);
            }
            personKey = DateTime.ParseExact(segments[0], KeyFormat, cultureInfo);
            if (results.ContainsKey(personKey))
                continue;
            results.Add(personKey, segments);
        }
        int countBefore = results.Count;
        DateTime minimumDateTime = results.Keys.Min();
        for (int i = 1; i < (1000 - countBefore); i++)
        {
            personKey = minimumDateTime.AddDays(i * -1);
            results.Add(personKey, new string[] { personKey.ToString(KeyFormat) });
        }
        return results.OrderBy(l => l.Key).ToDictionary(l => l.Key, l => l.Value);
    }

    internal static Dictionary<DateTime, PersonImport> GetPersonCollection(string knownPeopleFile)
    {
        Dictionary<DateTime, PersonImport> results = new();
        string name;
        DateTime key;
        string comment;
        string oldName;
        string mergeName;
        PersonImport person;
        Dictionary<DateTime, string[]> splitLines = Split(knownPeopleFile);
        foreach (KeyValuePair<DateTime, string[]> splitLine in splitLines)
        {
            name = string.Empty;
            key = splitLine.Key;
            comment = string.Empty;
            oldName = string.Empty;
            mergeName = string.Empty;
            if (splitLine.Value.Length > 1)
            {
                foreach (string segment in splitLine.Value)
                {
                    if (!segment.Contains('*'))
                        continue;
                    mergeName = segment.Split('*')[1].Split('\t')[0];
                }
                if (splitLine.Value[1].StartsWith("//"))
                    comment = splitLine.Value[1];
                else
                    name = splitLine.Value[1].Split('\t')[0];
                if (splitLine.Value.Length > 2)
                    comment = splitLine.Value[2];
            }
            person = new(key, name, mergeName, oldName, comment);
            results.Add(splitLine.Key, person);
        }
        return results;
    }

    internal static void SavePerson(Properties.IStorage storage, Models.Person person)
    {
        string fileName = IPerson.GetFileFullName(storage, person);
        string json = JsonSerializer.Serialize(person, new JsonSerializerOptions { WriteIndented = true });
        _ = IStorage.WriteAllText(fileName, json, updateDateWhenMatches: true, compareBeforeWrite: true);
    }

    private static List<Models.Person> GetPeopleFromText(Properties.IStorage storage, string localKnownPeopleFile)
    {
        List<Models.Person> results = new();
        string comment;
        Models.Person person;
        Models.PersonName name;
        List<Models.PersonURL> urls;
        Models.PersonBirthday birthday;
        List<Models.PersonEmail> emails = new();
        List<Models.PersonNumber> numbers = new();
        List<Models.PersonComment> comments = new();
        List<Models.PersonAddress> addresses = new();
        Dictionary<DateTime, PersonImport> keyValuePairs = GetPersonCollection(localKnownPeopleFile);
        foreach (KeyValuePair<DateTime, PersonImport> keyValuePair in keyValuePairs)
        {
            if (string.IsNullOrEmpty(keyValuePair.Value.Name))
                continue;
            urls = new();
            birthday = new(keyValuePair.Key);
            name = PersonName.Create(keyValuePair.Value.Name);
            if (name.First is null || string.IsNullOrEmpty(name.First.Value))
                continue;
            if (!string.IsNullOrEmpty(keyValuePair.Value.Comment))
            {
                comment = keyValuePair.Value.Comment[2..];
                if (!string.IsNullOrEmpty(comment))
                {
                    if (comment.StartsWith("http://") || comment.StartsWith("https://"))
                        urls.Add(new(new(comment)));
                    else
                        comments.Add(new(new(comment)));
                }
            }
            if (!string.IsNullOrEmpty(keyValuePair.Value.OldName))
                comments.Add(new(new(keyValuePair.Value.OldName)));
            person = IPerson.CreatePerson(storage, birthday, name, comments, urls, numbers, emails, addresses);
            SavePerson(storage, person);
            results.Add(person);
        }
        return results;
    }

    internal static Models.Person[] GetPeople(Properties.IStorage storage)
    {
        List<Models.Person> results = new();
        string json;
        string[] files;
        FileInfo fileInfo;
        Models.Person? person;
        string localKnownPeopleFile;
        DateTime dateTime = DateTime.MinValue;
        string directory = Path.Combine(storage.PeopleRootDirectory, "{}");
        if (!Directory.Exists(directory))
            _ = Directory.CreateDirectory(directory);
        string? rootDirectoryParent = Path.GetDirectoryName(storage.RootDirectory);
        if (string.IsNullOrEmpty(rootDirectoryParent))
            throw new NullReferenceException(nameof(rootDirectoryParent));
        if (!Directory.Exists(rootDirectoryParent))
            localKnownPeopleFile = string.Empty;
        else
        {
            files = Directory.GetFiles(rootDirectoryParent, "*People*.txt", SearchOption.TopDirectoryOnly);
            if (files.Any())
                localKnownPeopleFile = files[0];
            else
                localKnownPeopleFile = string.Empty;
        }
        files = Directory.GetFiles(directory, "*.json", SearchOption.TopDirectoryOnly);
        if (!files.Any() && string.IsNullOrEmpty(localKnownPeopleFile))
            throw new Exception("Copy \"KnownPeople.txt\" file from server!");
        foreach (string file in files)
        {
            fileInfo = new(file);
            if (dateTime < fileInfo.LastWriteTime)
                dateTime = fileInfo.LastWriteTime;
            json = File.ReadAllText(file);
            person = JsonSerializer.Deserialize<Models.Person>(json);
            if (person is null)
                continue;
            results.Add(person);
        }
        if (!results.Any())
            results = GetPeopleFromText(storage, localKnownPeopleFile);
        else if (!string.IsNullOrEmpty(localKnownPeopleFile))
        {
            fileInfo = new FileInfo(localKnownPeopleFile);
            if (fileInfo.LastWriteTime > dateTime)
            {
                foreach (string file in files)
                    File.Delete(file);
                results = GetPeopleFromText(storage, localKnownPeopleFile);
            }
        }
        return results.ToArray();
    }

}