using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace adas_core.Domain.Models;

/// <summary>
/// Serves as a base class for JSON converters that handle patient observation data, providing shared conversion behavior derived from <see cref="JsonConverter"/>.
/// </summary>
/// <remarks>
/// This type is intended to be subclassed by concrete converters that implement specific serialization and deserialization logic for patient observation types.
/// </remarks>
public class BasePatientObservation : JsonConverter
{
    public ObjectId Id { get; set; }
    public ObjectId PatientId { get; set; }
    public ObjectId? UserId { get; set; }

    public string? ClinicalEpisode { get; set; }

    public Patient? Patient { get; set; }

    public string? SystemId { get; set; }


    public string? Code { get; set; }


    public string? CodingSystem { get; set; }

    public ParentDataClass? ParentData { get; set; }


    public DateTime Time { get; set; }
    public DateTime? EndTime { get; set; }


    public string? Name { get; set; }


    public string? Units { get; set; }

    public bool CheckObservations { get; set; }

    public List<ConfigObservation>? CreateObservation { get; set; }

    /// <summary>
    /// Serializes the current object to a JSON string representation.
    /// Falls back to the base <see cref="object.ToString"/> implementation if JSON serialization fails.
    /// </summary>
    /// <returns>A JSON-formatted string of the object, or the result of <c>base.ToString()</c> if serialization throws an exception; may be <see langword="null"/> only if the underlying <c>ToString()</c> returns <see langword="null"/>.</returns>
    public string? ToJsonString()
        {
            try
            {
                return JsonConvert.SerializeObject(this, Formatting.None);
            }
            catch
            {
                return base.ToString();
            }
        }

    /// <summary>
    /// Returns a string representation of the patient observation, including its identifier, patient identifier, timestamp, and conditionally included optional fields (such as system identifier, code, coding system, end time, name, and units) when they have values. Any child creation observations are also appended to the output.
    /// </summary>
    /// <returns>A formatted string that lists the patient observation properties prefixed with <c>BasePatientObservation[</c> and suffixed with <c>]</c>.</returns>
    public override string ToString()
        {
            List<string> items =
            [
                $"id: {Id}",
                $"patientid: {PatientId}"
            ];
            if (!string.IsNullOrEmpty(SystemId)) items.Add($", systemId: {SystemId}");
            if (!string.IsNullOrEmpty(Code)) items.Add($"code: {Code}");
            if (!string.IsNullOrEmpty(CodingSystem)) items.Add($"codingSystem: {CodingSystem}");
            items.Add($"time: {Time}");
            if (EndTime.HasValue) items.Add($"endTime: {EndTime.Value}");
            if (!string.IsNullOrEmpty(Name)) items.Add($"name: {Name}");
            if (!string.IsNullOrEmpty(Units)) items.Add($"unit: {Units}");
            items.Add($"checkObservations: {CheckObservations}");
            CreateObservation?.ForEach(o => items.Add(o.ToString()));
    
            return "BasePatientObservation[" + string.Join(", ", items) + "]";
        }

    /// <summary>
    /// Converts the current object's properties into a list of formatted key-value strings, including core identifiers and timestamp, and conditionally appending optional fields only when they contain values.
    /// </summary>
    /// <returns>A list of strings where each entry is formatted as "key: value" representing the object's properties.</returns>
    public List<string> ToListString()
        {
            List<string> items =
            [
                $"id: {Id}",
                $"patientid: {PatientId}"
            ];
            if (!string.IsNullOrEmpty(SystemId)) items.Add($"systemId: {SystemId}");
            if (!string.IsNullOrEmpty(Code)) items.Add($"code: {Code}");
            if (!string.IsNullOrEmpty(CodingSystem)) items.Add($"codingSystem: {CodingSystem}");
            items.Add($"time: {Time}");
            if (!string.IsNullOrEmpty(Name)) items.Add($"name: {Name}");
            return items;
        }


    /// <summary>
    /// Determines whether the converter can handle the specified type by checking if it matches <see cref="BasePatientObservation"/>.
    /// </summary>
    /// <param name="objectType">The type to evaluate for conversion support.</param>
    /// <returns><c>true</c> if <paramref name="objectType"/> is <see cref="BasePatientObservation"/>; otherwise, <c>false</c>.</returns>
    public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(BasePatientObservation);
        }

    /// <summary>
    /// Serializes the specified object to JSON, excluding the <c>Id</c> and <c>Patient</c> properties from the output. If the value is <see langword="null"/>, the method returns without writing anything.
    /// </summary>
    /// <param name="writer">The <see cref="JsonWriter"/> to which the filtered JSON will be written.</param>
    /// <param name="value">The object to serialize. If <see langword="null"/>, nothing is written.</param>
    /// <param name="serializer">The <see cref="JsonSerializer"/> used during the conversion process.</param>
    public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
        {
            if (value == null) return;
    
            var jo = JObject.FromObject(value);
            jo.Property("Id")?.Remove();
            jo.Property("Patient")?.Remove();
            jo.WriteTo(writer);
        }

    /// <summary>
    /// Reads the JSON representation of an object. The current implementation is not provided and will always throw a <see cref="NotImplementedException"/>.
    /// </summary>
    /// <param name="reader">The reader used to parse the JSON content.</param>
    /// <param name="objectType">The type of the object to deserialize.</param>
    /// <param name="existingValue">The existing value of the object being deserialized.</param>
    /// <param name="serializer">The serializer instance performing the deserialization.</param>
    /// <returns>An object instance built from the JSON data.</returns>
    /// <exception cref="NotImplementedException">Always thrown because the method has not been implemented.</exception>
    public override object ReadJson(JsonReader reader, Type objectType, object? existingValue,
            JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
}

/// <summary>
/// Represents a parent data class that serves as a base container for shared data members and functionality.
/// Provides a foundational structure intended to be inherited by more specific data classes.
/// </summary>
public class ParentDataClass
{
    public string? Code { get; set; }

    public string? CodingSystem { get; set; }

    public string? Name { get; set; }
}