using Firely.Fhir.Packages;
using Firely.Fhir.Validation;
using Hl7.Fhir.Model;
using Hl7.Fhir.Serialization;
using Hl7.Fhir.Specification.Snapshot;
using Hl7.Fhir.Specification.Source;
using Hl7.Fhir.Specification.Terminology;
using Hl7.Fhir.Support;
using System.Text.RegularExpressions;

class Program
{
    /// <summary>
    /// Reads FHIR STU3 (or R4, etc) structure definitions, validates and optionally converts between JSON and XML.
    /// Generetes snapshots and templated date-times for Nictiz StructureDefinitions.
    /// Takes out warnings on termilogy, but could use an import of SNOMED, LOINC, etc.
    /// </summary>
    static int Main(string[] args)
    {
        bool export = true;
        string rootPath = Path.Combine(Directory.GetCurrentDirectory(), "..\\..\\..\\..\\");
        string resourcesPath = Path.GetFullPath(Path.Combine(rootPath, "resources\\import")); // https://github.com/Nictiz/Nictiz-testscripts/tree/main/output/STU3/BgZ-3-0/MedMij/Test/_reference/resources
        string resourcesXmlPath = Path.GetFullPath(Path.Combine(rootPath, "resources\\xml"));
        string resourcesJsonPath = Path.GetFullPath(Path.Combine(rootPath, "resources\\json"));
        string nictizPackagePath = Path.Combine(Directory.GetCurrentDirectory(), "nictiz.fhir.nl.stu3.zib2017-2.3.0.tgz");

        var stu3 = ZipSource.CreateValidationSource(); // specification.zip
        var nictiz = new FhirPackageSource(ModelInfo.ModelInspector, new[] { nictizPackagePath });
        var multiResolver = new MultiResolver(nictiz, stu3);
        var resolver = new CachedResolver(new SnapshotGeneratingResolver(multiResolver)); // Solves "StructureDefinition has no snapshot component. (Parameter 'sd')"
        ICodeValidationTerminologyService terminology = new LocalTerminologyService(resolver);

        var settings = new ValidationSettings
        {
            ConformanceResourceResolver = resolver,
            ValidateCodeService = terminology
        };
        settings.SetSkipConstraintValidation(false);
        var validator = new Validator(resolver, terminology, settings: settings);


        string[] filenames = Directory.GetFiles(resourcesPath, "*.*");
        var parserXml = new FhirXmlParser();
        var parserJson = new FhirJsonParser();        
        var serializerXml = new FhirXmlSerializer(new() { Pretty = true });
        var serializerJson = new FhirJsonSerializer(new() { Pretty = true });
        var errorcount = 0;

        foreach (var filename in filenames)
        {
            Console.WriteLine(Path.GetFileName(filename));
            try
            {
                // Read
                var text = File.ReadAllText(filename).PopulateNictizTemplate();
                var resource = (filename.EndsWith(".xml") ? parserXml.Parse<Resource>(text) : parserJson.Parse<Resource>(text));
                var outcome = validator.Validate(resource); // resource.ToTypedElement()

                // Validate
                Func<OperationOutcome.IssueComponent, bool> isNotTerminologyWarnings = issue => issue.Severity == OperationOutcome.IssueSeverity.Warning && issue.Code != OperationOutcome.IssueType.CodeInvalid && issue.Details.Text.Contains("terminology");
                foreach (var issue in outcome.Issue.Where(isNotTerminologyWarnings))
                {
                    errorcount++;
                    Console.WriteLine($" - {issue.Code.Value} {issue.Severity}: {issue.Details?.Text}");
                }

                // Export
                if (export)
                {
                    var exportname = Path.GetFileNameWithoutExtension(filename);
                    var exportnameJson = Path.Combine(resourcesJsonPath, exportname + ".json");
                    var exportnameXml = Path.Combine(resourcesXmlPath, exportname + ".xml");
                    var json = serializerJson.SerializeToString(resource);
                    var xml = serializerXml.SerializeToString(resource);
                    File.WriteAllText(exportnameJson, json);
                    File.WriteAllText(exportnameXml, xml);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($" - {ex.Message}");
                continue;
            }
        }

        Console.WriteLine($"{errorcount} validatiefounten gevonden.");
        Console.WriteLine($"{(export ? $"{filenames.Count()} exports" : "Geen export")} gemaakt naar FHIR XML en FHIR JSON.");
        return 0;
    }
}

public static class TemplateExtensions
{
    /// <summary>
    /// Replaces Nictiz date template placeholders in the input string with calculated dates.
    /// Recognizes placeholders of the form ${DATE, T, D, +/-N}(T00:00:00+hh:mm) and replaces them with the current date offset by N days.
    /// </summary>
    public static string PopulateNictizTemplate(this string template)
    {
        return Regex.Replace(template, @"\$\{DATE, T, D, ([^}]+)\}(T00:00:00([+-]\d{2}:\d{2}))?",
            match =>
            {
                var offsetText = match.Groups[1].Value.Trim();
                int days = 0;
                if (offsetText.StartsWith("+"))
                {
                    int.TryParse(offsetText.Substring(1), out days);
                }
                else if (offsetText.StartsWith("-"))
                {
                    int.TryParse(offsetText, out days);
                }

                var date = DateTimeOffset.Now.Date.AddDays(days);
                var timeAndZone = match.Groups[2].Success ? match.Groups[2].Value : string.Empty;
                return date.ToString("yyyy-MM-dd") + (string.IsNullOrEmpty(timeAndZone) ? timeAndZone : "");
            });
    }
}

public class SnapshotGeneratingResolver : IResourceResolver
{
    private readonly IResourceResolver _inner;
    private readonly SnapshotGenerator _generator;

    public SnapshotGeneratingResolver(IResourceResolver inner)
    {
        _inner = inner;
        _generator = new SnapshotGenerator(inner);
    }

    public Resource ResolveByUri(string uri)
    {
        var resource = _inner.ResolveByUri(uri);
        return EnsureSnapshot(resource);
    }

    public Resource ResolveByCanonicalUri(string uri)
    {
        var resource = _inner.ResolveByCanonicalUri(uri);
        return EnsureSnapshot(resource);
    }

    private Resource EnsureSnapshot(Resource resource)
    {
        if (resource is StructureDefinition sd && (sd.Snapshot == null || sd.Snapshot.Element == null || sd.Snapshot.Element.Count == 0))
        {
            _generator.Update(sd);
        }
        return resource;
    }
}

