using adas_core.Domain.Enums;
using adas_core.Domain.Models.MongoModels;

namespace adas_core.Domain.Models;

/// <summary>
/// Represents a person, providing type-specific equality comparison between instances.
/// </summary>
/// <remarks>
/// Implements <see cref="IEquatable{T}"/> to define a strongly typed Equals(T)"/> method for comparing <see cref="Person"/> objects.
/// </remarks>
public class Person : IEquatable<Person>
{
    public string? LastName { get; set; }

    public string? FirstName { get; set; }

    public string? SecondName { get; set; }

    public DateTime? BirthDate { get; set; }

    public string? Language { get; set; }

    public PatientEnum.Gender Gender { get; set; } = PatientEnum.Gender.Unknown;

    public Dictionary<string, string>? Ids { get; set; }


    public List<HistoricalId>? HistoricalIds { get; set; }

    /// <summary>
    /// Determines whether the current <see cref="Person"/> instance is equal to another <see cref="Person"/> by comparing their last name, first name, second name, birth date, language, gender, and identifiers.
    /// Returns <c>false</c> when the specified other person is <c>null</c>.
    /// </summary>
    /// <param name="other">The <see cref="Person"/> to compare with the current instance.</param>
    /// <returns><c>true</c> if all compared personal attributes and identifiers match; otherwise, <c>false</c>.</returns>
    public bool Equals(Person? other)
        {
            return other != null &&
                   LastName == other.LastName &&
                   FirstName == other.FirstName &&
                   SecondName == other.SecondName &&
                   BirthDate == other.BirthDate &&
                   Language == other.Language &&
                   Gender == other.Gender && CheckIdsAreEqual(other);
        }

    /// <summary>
    /// Sets the current patient identifiers and records them in the historical identifier list. If a historical entry already exists for the target time, the provided identifiers are merged into it; otherwise a new entry is created. When <paramref name="time"/> is <c>null</c>, the current UTC date and time is used, and the historical list is lazily initialized if needed.
    /// </summary>
    /// <param name="patientIds">The dictionary of patient identifiers to assign as the current identifiers and to record historically.</param>
    /// <param name="time">The optional timestamp for the historical entry. If <c>null</c>, <see cref="DateTime.UtcNow"/> is used.</param>
    public void SetIds(Dictionary<string, string> patientIds, DateTime? time = null)
        {
            Ids = patientIds;
            HistoricalIds ??= new List<HistoricalId>();
        
            DateTime targetTime = time ?? DateTime.UtcNow;
    
            var existingOption = HistoricalIds.FirstOrDefault(c => c.Time == targetTime);
    
            if (existingOption != null)
            {
                foreach (var kvp in patientIds)
                {
                    existingOption.PatientIds[kvp.Key] = kvp.Value;
                }
            }
            else
            {
                HistoricalIds.Add(new HistoricalId(targetTime, new Dictionary<string, string>(patientIds)));
            }
        }

    /// <summary>
    /// Determines whether the patient instance contains no meaningful data by checking that the last name, first name, second name, and birth date are unset, the gender is unknown, and the identifiers collection is null or empty.
    /// </summary>
    /// <returns><c>true</c> if all relevant patient fields are null, empty, or set to their default unknown value; otherwise, <c>false</c>.</returns>
    public bool IsEmpty()
        {
            return string.IsNullOrEmpty(LastName) && string.IsNullOrEmpty(FirstName) && string.IsNullOrEmpty(SecondName) &&
                   BirthDate == null && Gender == PatientEnum.Gender.Unknown && (Ids == null || Ids.Count == 0);
        }

    /// <summary>
    /// Determines whether the patient record contains no personal information, ignoring identification fields.
    /// Returns true only when all personal data fields (last name, first name, second name, birth date) are null or empty
    /// and the gender is set to <see cref="PatientEnum.Gender.Unknown"/>.
    /// </summary>
    /// <returns><c>true</c> if the patient's personal data fields are empty and gender is unknown; otherwise, <c>false</c>.</returns>
    public bool IsEmptyDontCheckIds()
        {
            return string.IsNullOrEmpty(LastName) && string.IsNullOrEmpty(FirstName) && string.IsNullOrEmpty(SecondName) &&
                   BirthDate == null && Gender == PatientEnum.Gender.Unknown;
        }

    //public override bool Equals(object? obj)
    //{
    //    return Equals(obj as Person);
    //}

    /// <summary>
    /// Determines whether the identifier collection of the current <see cref="Person"/> is equal to that of another instance, handling cases where either or both collections are <c>null</c> and verifying matching keys and values.
    /// </summary>
    /// <param name="other">The other <see cref="Person"/> whose identifiers are compared against this instance.</param>
    /// <returns><c>true</c> if both identifier collections are <c>null</c> or contain the same key-value pairs; otherwise, <c>false</c>.</returns>
    private bool CheckIdsAreEqual(Person other)
        {
            if (Ids == null && other.Ids == null) return true;
            if (Ids == null || other.Ids == null) return false;
            if (Ids.Count != other.Ids.Count) return false;
    
            foreach (var kvp in Ids)
                if (!other.Ids.TryGetValue(kvp.Key, out var otherValue) || otherValue != kvp.Value)
                    return false;
            return true;
        }

    //public override int GetHashCode()
    //{
    //    var hashCode = -373112803;
    //    if (LastName != null)
    //        hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(LastName);
    //    if (FirstName != null)
    //        hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(FirstName);
    //    if (SecondName != null)
    //        hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(SecondName);
    //    if (BirthDate != null) hashCode = hashCode * -1521134295 + BirthDate.GetHashCode();
    //    if (Language != null)
    //        hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Language);
    //    hashCode = hashCode * -1521134295 + Gender.GetHashCode();
    //    if (Ids != null)
    //        hashCode = hashCode * -1521134295 + EqualityComparer<Dictionary<string, string>>.Default.GetHashCode(Ids);
    //    return hashCode;
    //}

    /// <summary>
    /// Returns a string representation of the Person, including the non-empty/non-null properties such as first name, last name, second name, birth date, language, gender, and identifiers.
    /// </summary>
    /// <returns>A formatted string in the form "Person[prop1: value1, prop2: value2, ...]" containing the person's populated fields.</returns>
    public override string ToString()
        {
            List<string> items = [];
            if (!string.IsNullOrEmpty(FirstName)) items.Add($"firstName: {FirstName}");
            if (!string.IsNullOrEmpty(LastName)) items.Add($", lastName: {LastName}");
            if (!string.IsNullOrEmpty(SecondName)) items.Add($"secondName: {SecondName}");
            if (BirthDate.HasValue) items.Add($"birthDate: {BirthDate}");
            if (!string.IsNullOrEmpty(Language)) items.Add($"language: {Language}");
            items.Add($"gender: {Gender}");
            if (Ids?.Count > 0) items.Add($"ids: {string.Join(',', Ids.Select(i => $"[{i.Key}:{i.Value}]"))}");
            return "Person[" + string.Join(", ", items) + "]";
        }
}