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

/// <summary>
/// FHIR R4 read and search interactions for Patient and Encounter resources.
/// </summary>
[ApiController]
[Route("fhir/R4")]
[AuthorizePermission(ClinicalPermissions.FhirRead)]
[ServiceFilter(typeof(FhirExceptionFilter))]
public class FhirReadController : ControllerBase
{
    private static readonly FhirJsonSerializer Serializer = new();

    private readonly AppDbContext _db;
    private readonly IExternalIdentifierService _identifiers;
    private readonly PatientFhirMapper _patientMapper;
    private readonly EncounterFhirMapper _encounterMapper;
    private readonly FhirOptions _options;
    private readonly ClinicalMetrics _metrics;

    public FhirReadController(
        AppDbContext db,
        IExternalIdentifierService identifiers,
        PatientFhirMapper patientMapper,
        EncounterFhirMapper encounterMapper,
        IOptions<FhirOptions> options,
        ClinicalMetrics metrics)
    {
        _db = db;
        _identifiers = identifiers;
        _patientMapper = patientMapper;
        _encounterMapper = encounterMapper;
        _options = options.Value;
        _metrics = metrics;
    }

    /// <summary>
    /// Reads a Patient resource by internal ID.
    /// </summary>
    [HttpGet("Patient/{id:guid}")]
    [Produces("application/fhir+json")]
    [ProducesResponseType(typeof(Hl7.Fhir.Model.Patient), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(OperationOutcome), StatusCodes.Status404NotFound)]
    public async Task<IActionResult> ReadPatient(Guid id)
    {
        var patient = await _db.Patients.AsNoTracking().FirstOrDefaultAsync(p => p.Id == id);
        if (patient is null)
            throw new NotFoundException("Patient not found.", "FHIR_RESOURCE_NOT_FOUND");

        var hospitalId = await _identifiers.FindPrimaryIdentifierAsync(
            ExternalResourceType.Patient, patient.Id, _options.PatientIdentifierSystems);

        var resource = _patientMapper.ToFhirResponse(patient, hospitalId);
        _metrics.FhirReadTotal.WithLabels("Patient", "read", "success").Inc();
        return Serialize(resource);
    }

    /// <summary>
    /// Searches for Patient resources by identifier (system|value).
    /// </summary>
    [HttpGet("Patient")]
    [Produces("application/fhir+json")]
    [ProducesResponseType(typeof(Bundle), StatusCodes.Status200OK)]
    public async Task<IActionResult> SearchPatient(
        [FromQuery] string? identifier,
        [FromQuery] int _count = 20)
    {
        _count = Math.Clamp(_count, 1, 100);

        var bundle = new Bundle
        {
            Type = Bundle.BundleType.Searchset,
            Timestamp = DateTimeOffset.UtcNow
        };

        if (identifier is not null)
        {
            var parts = identifier.Split('|', 2);
            if (parts.Length == 2 && !string.IsNullOrWhiteSpace(parts[0]) && !string.IsNullOrWhiteSpace(parts[1]))
            {
                var internalId = await _identifiers.ResolveInternalIdAsync(
                    ExternalResourceType.Patient, parts[0], parts[1]);

                if (internalId.HasValue)
                {
                    var patient = await _db.Patients.AsNoTracking().FirstOrDefaultAsync(p => p.Id == internalId.Value);
                    if (patient is not null)
                    {
                        var hospitalId = await _identifiers.FindPrimaryIdentifierAsync(
                            ExternalResourceType.Patient, patient.Id, _options.PatientIdentifierSystems);
                        var resource = _patientMapper.ToFhirResponse(patient, hospitalId);
                        bundle.Entry.Add(new Bundle.EntryComponent
                        {
                            FullUrl = $"{Request.Scheme}://{Request.Host}/fhir/R4/Patient/{patient.Id}",
                            Resource = resource
                        });
                    }
                }
            }
        }
        else
        {
            var patients = await _db.Patients
                .AsNoTracking()
                .OrderBy(p => p.LastName).ThenBy(p => p.FirstName)
                .Take(_count)
                .ToListAsync();

            foreach (var patient in patients)
            {
                var hospitalId = await _identifiers.FindPrimaryIdentifierAsync(
                    ExternalResourceType.Patient, patient.Id, _options.PatientIdentifierSystems);
                var resource = _patientMapper.ToFhirResponse(patient, hospitalId);
                bundle.Entry.Add(new Bundle.EntryComponent
                {
                    FullUrl = $"{Request.Scheme}://{Request.Host}/fhir/R4/Patient/{patient.Id}",
                    Resource = resource
                });
            }
        }

        bundle.Total = bundle.Entry.Count;
        _metrics.FhirReadTotal.WithLabels("Patient", "search", "success").Inc();
        return Serialize(bundle);
    }

    /// <summary>
    /// Reads an Encounter resource by internal ID.
    /// </summary>
    [HttpGet("Encounter/{id:guid}")]
    [Produces("application/fhir+json")]
    [ProducesResponseType(typeof(Hl7.Fhir.Model.Encounter), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(OperationOutcome), StatusCodes.Status404NotFound)]
    public async Task<IActionResult> ReadEncounter(Guid id)
    {
        var encounter = await _db.Encounters
            .AsNoTracking()
            .Include(e => e.Patient)
            .FirstOrDefaultAsync(e => e.Id == id);

        if (encounter is null)
            throw new NotFoundException("Encounter not found.", "FHIR_RESOURCE_NOT_FOUND");

        var hospitalId = await _identifiers.FindPrimaryIdentifierAsync(
            ExternalResourceType.Encounter, encounter.Id, _options.EncounterIdentifierSystems);

        var resource = _encounterMapper.ToFhirResponse(encounter, hospitalId);
        _metrics.FhirReadTotal.WithLabels("Encounter", "read", "success").Inc();
        return Serialize(resource);
    }

    /// <summary>
    /// Searches for Encounter resources by patient reference.
    /// </summary>
    [HttpGet("Encounter")]
    [Produces("application/fhir+json")]
    [ProducesResponseType(typeof(Bundle), StatusCodes.Status200OK)]
    public async Task<IActionResult> SearchEncounter(
        [FromQuery] string? patient,
        [FromQuery] string? status,
        [FromQuery] int _count = 20)
    {
        _count = Math.Clamp(_count, 1, 100);

        var query = _db.Encounters.AsNoTracking().Include(e => e.Patient).AsQueryable();

        if (patient is not null && Guid.TryParse(patient, out var patientId))
            query = query.Where(e => e.PatientId == patientId);

        if (status is not null)
        {
            var fhirStatus = status.ToLowerInvariant() switch
            {
                "in-progress" => EncounterStatus.Active,
                "finished" => EncounterStatus.Discharged,
                "cancelled" => EncounterStatus.Cancelled,
                _ => (EncounterStatus?)null
            };
            if (fhirStatus.HasValue)
                query = query.Where(e => e.Status == fhirStatus.Value);
        }

        var encounters = await query
            .OrderByDescending(e => e.AdmittedAt)
            .Take(_count)
            .ToListAsync();

        var bundle = new Bundle
        {
            Type = Bundle.BundleType.Searchset,
            Total = encounters.Count,
            Timestamp = DateTimeOffset.UtcNow
        };

        foreach (var encounter in encounters)
        {
            var hospitalId = await _identifiers.FindPrimaryIdentifierAsync(
                ExternalResourceType.Encounter, encounter.Id, _options.EncounterIdentifierSystems);
            var resource = _encounterMapper.ToFhirResponse(encounter, hospitalId);
            bundle.Entry.Add(new Bundle.EntryComponent
            {
                FullUrl = $"{Request.Scheme}://{Request.Host}/fhir/R4/Encounter/{encounter.Id}",
                Resource = resource
            });
        }

        _metrics.FhirReadTotal.WithLabels("Encounter", "search", "success").Inc();
        return Serialize(bundle);
    }

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