using Hl7.Fhir.Model;
using Microsoft.EntityFrameworkCore;

public class FhirBundleProcessor
{
    private readonly AppDbContext _db;
    private readonly IPatientService _patients;
    private readonly IEncounterService _encounters;
    private readonly IObservationService _observations;
    private readonly IMedicationService _medications;
    private readonly PatientFhirMapper _patientMapper;
    private readonly EncounterFhirMapper _encounterMapper;
    private readonly ObservationFhirMapper _observationMapper;
    private readonly MedicationAdministrationFhirMapper _medMapper;

    public FhirBundleProcessor(
        AppDbContext db,
        IPatientService patients,
        IEncounterService encounters,
        IObservationService observations,
        IMedicationService medications,
        PatientFhirMapper patientMapper,
        EncounterFhirMapper encounterMapper,
        ObservationFhirMapper observationMapper,
        MedicationAdministrationFhirMapper medMapper)
    {
        _db = db;
        _patients = patients;
        _encounters = encounters;
        _observations = observations;
        _medications = medications;
        _patientMapper = patientMapper;
        _encounterMapper = encounterMapper;
        _observationMapper = observationMapper;
        _medMapper = medMapper;
    }

    public async Task<Bundle> ProcessTransactionAsync(Bundle transaction)
    {
        var response = new Bundle { Type = Bundle.BundleType.TransactionResponse };

        var entries = transaction.Entry
            .OrderBy(e => Priority(e.Resource))
            .ToList();

        await using var tx = await _db.Database.BeginTransactionAsync();

        foreach (var entry in entries)
        {
            var resource = entry.Resource;
            try
            {
                var location = resource switch
                {
                    Hl7.Fhir.Model.Patient p => await ProcessPatientAsync(p),
                    Hl7.Fhir.Model.Encounter e => await ProcessEncounterAsync(e),
                    Hl7.Fhir.Model.Observation o => await ProcessObservationAsync(o),
                    Hl7.Fhir.Model.MedicationAdministration m => await ProcessMedAsync(m),
                    _ => throw new FhirMappingException(
                        $"Unsupported resource type in bundle: {resource.TypeName}", "not-supported")
                };

                response.Entry.Add(new Bundle.EntryComponent
                {
                    Response = new Bundle.ResponseComponent
                    {
                        Status = "201 Created",
                        Location = location
                    }
                });
            }
            catch (Exception ex)
            {
                await tx.RollbackAsync();

                response.Entry.Add(new Bundle.EntryComponent
                {
                    Response = new Bundle.ResponseComponent
                    {
                        Status = "422 Unprocessable Entity",
                        Outcome = FhirOperationOutcomeBuilder.FromException(ex)
                    }
                });
                return response;
            }
        }

        await tx.CommitAsync();
        return response;
    }

    private static int Priority(Resource? r) => r switch
    {
        Hl7.Fhir.Model.Patient => 0,
        Hl7.Fhir.Model.Encounter => 1,
        _ => 2
    };

    private async Task<string> ProcessPatientAsync(Hl7.Fhir.Model.Patient fhir)
    {
        var req = _patientMapper.ToUpsertRequest(fhir);
        var patient = await _patients.RegisterOrUpdateByIdentifierAsync(req);
        return $"Patient/{patient.Id}";
    }

    private async Task<string> ProcessEncounterAsync(Hl7.Fhir.Model.Encounter fhir)
    {
        var req = await _encounterMapper.ToUpsertRequestAsync(fhir);
        var encounter = await _encounters.OpenOrUpdateByIdentifierAsync(req);
        return $"Encounter/{encounter.Id}";
    }

    private async Task<string> ProcessObservationAsync(Hl7.Fhir.Model.Observation fhir)
    {
        var mapped = await _observationMapper.ToIngestRequestsAsync(fhir);
        Guid? lastId = null;
        foreach (var item in mapped)
        {
            var result = await _observations.IngestAsync(item.EncounterId, item.Request);
            lastId = result.Observation.Id;
        }
        return $"Observation/{lastId}";
    }

    private async Task<string> ProcessMedAsync(Hl7.Fhir.Model.MedicationAdministration fhir)
    {
        var (req, encounterId) = await _medMapper.ToCreateRequestAsync(fhir);
        var med = await _medications.CreateAsync(encounterId, req);
        return $"MedicationAdministration/{med.Id}";
    }
}