using Hl7.Fhir.Model;
using Hl7.Fhir.Serialization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;

/// <summary>
/// FHIR R4 inbound facade: single-resource create and transaction Bundle processing.
/// </summary>
[ApiController]
[Route("fhir/R4")]
[AuthorizePermission(ClinicalPermissions.FhirIngest)]
[ServiceFilter(typeof(FhirExceptionFilter))]
public class FhirIngestController : ControllerBase
{
    private static readonly FhirJsonParser Parser = new();
    private static readonly FhirJsonSerializer Serializer = new();

    private readonly IPatientService _patients;
    private readonly IEncounterService _encounters;
    private readonly IObservationService _observations;
    private readonly IMedicationService _medications;
    private readonly IExternalIdentifierService _identifiers;
    private readonly PatientFhirMapper _patientMapper;
    private readonly EncounterFhirMapper _encounterMapper;
    private readonly ObservationFhirMapper _observationMapper;
    private readonly MedicationAdministrationFhirMapper _medMapper;
    private readonly FhirBundleProcessor _bundleProcessor;
    private readonly FhirOptions _options;
    private readonly ClinicalMetrics _metrics;

    public FhirIngestController(
        IPatientService patients,
        IEncounterService encounters,
        IObservationService observations,
        IMedicationService medications,
        IExternalIdentifierService identifiers,
        PatientFhirMapper patientMapper,
        EncounterFhirMapper encounterMapper,
        ObservationFhirMapper observationMapper,
        MedicationAdministrationFhirMapper medMapper,
        FhirBundleProcessor bundleProcessor,
        IOptions<FhirOptions> options,
        ClinicalMetrics metrics)
    {
        _patients = patients;
        _encounters = encounters;
        _observations = observations;
        _medications = medications;
        _identifiers = identifiers;
        _patientMapper = patientMapper;
        _encounterMapper = encounterMapper;
        _observationMapper = observationMapper;
        _medMapper = medMapper;
        _bundleProcessor = bundleProcessor;
        _options = options.Value;
        _metrics = metrics;
    }

    /// <summary>
    /// Creates or updates a Patient from a FHIR R4 Patient resource (idempotent by identifier).
    /// </summary>
    /// <returns>The persisted Patient resource with Location header.</returns>
    [HttpPost("Patient")]
    [Consumes("application/fhir+json")]
    [Produces("application/fhir+json")]
    [ProducesResponseType(typeof(Hl7.Fhir.Model.Patient), StatusCodes.Status201Created)]
    [ProducesResponseType(typeof(OperationOutcome), StatusCodes.Status404NotFound)]
    [ProducesResponseType(typeof(OperationOutcome), StatusCodes.Status422UnprocessableEntity)]
    [ProducesResponseType(typeof(OperationOutcome), StatusCodes.Status409Conflict)]
    [ProducesResponseType(typeof(OperationOutcome), StatusCodes.Status500InternalServerError)]
    public async Task<IActionResult> CreatePatient()
    {
        var fhir = await ParseBodyAsync<Hl7.Fhir.Model.Patient>();
        var req = _patientMapper.ToUpsertRequest(fhir);
        var patient = await _patients.RegisterOrUpdateByIdentifierAsync(req);
        var hospitalId = await _identifiers.FindPrimaryIdentifierAsync(
            ExternalResourceType.Patient, patient.Id, _options.PatientIdentifierSystems);
        var response = _patientMapper.ToFhirResponse(patient, hospitalId);
        _metrics.FhirIngestTotal.WithLabels("Patient", "success").Inc();
        Response.Headers.Location = $"{Request.Path}/{patient.Id}";
        return Serialize(response, StatusCodes.Status201Created);
    }

    /// <summary>
    /// Creates or updates an Encounter from a FHIR R4 Encounter resource (idempotent by identifier).
    /// </summary>
    /// <returns>The persisted Encounter resource with Location header.</returns>
    [HttpPost("Encounter")]
    [Consumes("application/fhir+json")]
    [Produces("application/fhir+json")]
    [ProducesResponseType(typeof(Hl7.Fhir.Model.Encounter), StatusCodes.Status201Created)]
    [ProducesResponseType(typeof(OperationOutcome), StatusCodes.Status404NotFound)]
    [ProducesResponseType(typeof(OperationOutcome), StatusCodes.Status422UnprocessableEntity)]
    [ProducesResponseType(typeof(OperationOutcome), StatusCodes.Status409Conflict)]
    [ProducesResponseType(typeof(OperationOutcome), StatusCodes.Status500InternalServerError)]
    public async Task<IActionResult> CreateEncounter()
    {
        var fhir = await ParseBodyAsync<Hl7.Fhir.Model.Encounter>();
        var req = await _encounterMapper.ToUpsertRequestAsync(fhir);
        var encounter = await _encounters.OpenOrUpdateByIdentifierAsync(req);
        var hospitalId = await _identifiers.FindPrimaryIdentifierAsync(
            ExternalResourceType.Encounter, encounter.Id, _options.EncounterIdentifierSystems);
        var response = _encounterMapper.ToFhirResponse(encounter, hospitalId);
        _metrics.FhirIngestTotal.WithLabels("Encounter", "success").Inc();
        Response.Headers.Location = $"{Request.Path}/{encounter.Id}";
        return Serialize(response, StatusCodes.Status201Created);
    }

    /// <summary>
    /// Ingests one or more observations from a FHIR R4 Observation resource.
    /// </summary>
    /// <returns>The last persisted Observation resource with Location header.</returns>
    [HttpPost("Observation")]
    [Consumes("application/fhir+json")]
    [Produces("application/fhir+json")]
    [ProducesResponseType(typeof(Hl7.Fhir.Model.Observation), StatusCodes.Status201Created)]
    [ProducesResponseType(typeof(OperationOutcome), StatusCodes.Status404NotFound)]
    [ProducesResponseType(typeof(OperationOutcome), StatusCodes.Status422UnprocessableEntity)]
    [ProducesResponseType(typeof(OperationOutcome), StatusCodes.Status409Conflict)]
    [ProducesResponseType(typeof(OperationOutcome), StatusCodes.Status500InternalServerError)]
    public async Task<IActionResult> CreateObservation()
    {
        var fhir = await ParseBodyAsync<Hl7.Fhir.Model.Observation>();
        var mapped = await _observationMapper.ToIngestRequestsAsync(fhir);

        Resource? lastResponse = null;
        foreach (var item in mapped)
        {
            var result = await _observations.IngestAsync(item.EncounterId, item.Request);
            lastResponse = new Hl7.Fhir.Model.Observation
            {
                Id = result.Observation.Id.ToString(),
                Status = ObservationStatus.Final,
                Code = new CodeableConcept("http://loinc.org", item.Request.ObservationCode),
                Value = new Quantity(item.Request.Value, item.Request.Unit)
            };
        }

        _metrics.FhirIngestTotal.WithLabels("Observation", "success").Inc();
        Response.Headers.Location = Request.Path.Value!;
        return Serialize(lastResponse!, StatusCodes.Status201Created);
    }

    /// <summary>
    /// Records a medication administration from a FHIR R4 MedicationAdministration resource.
    /// </summary>
    /// <returns>The persisted MedicationAdministration resource with Location header.</returns>
    [HttpPost("MedicationAdministration")]
    [Consumes("application/fhir+json")]
    [Produces("application/fhir+json")]
    [ProducesResponseType(typeof(Hl7.Fhir.Model.MedicationAdministration), StatusCodes.Status201Created)]
    [ProducesResponseType(typeof(OperationOutcome), StatusCodes.Status404NotFound)]
    [ProducesResponseType(typeof(OperationOutcome), StatusCodes.Status422UnprocessableEntity)]
    [ProducesResponseType(typeof(OperationOutcome), StatusCodes.Status409Conflict)]
    [ProducesResponseType(typeof(OperationOutcome), StatusCodes.Status500InternalServerError)]
    public async Task<IActionResult> CreateMedicationAdministration()
    {
        var fhir = await ParseBodyAsync<Hl7.Fhir.Model.MedicationAdministration>();
        var (req, encounterId) = await _medMapper.ToCreateRequestAsync(fhir);
        var med = await _medications.CreateAsync(encounterId, req);
        var response = new Hl7.Fhir.Model.MedicationAdministration { Id = med.Id.ToString() };
        _metrics.FhirIngestTotal.WithLabels("MedicationAdministration", "success").Inc();
        Response.Headers.Location = $"{Request.Path}/{med.Id}";
        return Serialize(response, StatusCodes.Status201Created);
    }

    /// <summary>
    /// Processes a FHIR R4 transaction Bundle (e.g. ADT admit with Patient + Encounter).
    /// </summary>
    /// <returns>A transaction-response Bundle with per-entry outcomes.</returns>
    [HttpPost]
    [Consumes("application/fhir+json")]
    [Produces("application/fhir+json")]
    [ProducesResponseType(typeof(Bundle), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(OperationOutcome), StatusCodes.Status404NotFound)]
    [ProducesResponseType(typeof(OperationOutcome), StatusCodes.Status422UnprocessableEntity)]
    [ProducesResponseType(typeof(OperationOutcome), StatusCodes.Status409Conflict)]
    [ProducesResponseType(typeof(OperationOutcome), StatusCodes.Status500InternalServerError)]
    public async Task<IActionResult> ProcessBundle()
    {
        using var reader = new StreamReader(Request.Body);
        var json = await reader.ReadToEndAsync();
        var bundle = Parser.Parse<Bundle>(json);

        if (bundle.Type != Bundle.BundleType.Transaction)
            throw new FhirMappingException("Only transaction Bundles are supported.", "not-supported");

        var responseBundle = await _bundleProcessor.ProcessTransactionAsync(bundle);
        _metrics.FhirIngestTotal.WithLabels("Bundle", "success").Inc();
        return Serialize(responseBundle);
    }

    private async Task<T> ParseBodyAsync<T>() where T : Resource
    {
        using var reader = new StreamReader(Request.Body);
        var json = await reader.ReadToEndAsync();
        return Parser.Parse<T>(json);
    }

    private ContentResult Serialize(Resource resource, int statusCode = StatusCodes.Status200OK) =>
        new()
        {
            Content = Serializer.SerializeToString(resource),
            ContentType = "application/fhir+json",
            StatusCode = statusCode
        };
}
