using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.IndexManagement;
using Microsoft.Extensions.Options;

public class ElasticIndexProvisioner : IHostedService
{
    private readonly ElasticsearchClient _elastic;
    private readonly ElasticsearchOptions _options;
    private readonly ILogger<ElasticIndexProvisioner> _logger;

    public ElasticIndexProvisioner(
        ElasticsearchClient elastic,
        IOptions<ElasticsearchOptions> options,
        ILogger<ElasticIndexProvisioner> logger)
    {
        _elastic = elastic;
        _options = options.Value;
        _logger = logger;
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        await EnsureIndexAsync<PatientEncounterDocument>(
            _options.Indices.PatientEncounters, BuildPatientEncountersMapping());
        await EnsureIndexAsync<ObservationDocument>(
            _options.Indices.Observations, BuildObservationsMapping());
        await EnsureIndexAsync<ClinicalAlertDocument>(
            _options.Indices.ClinicalAlerts, BuildClinicalAlertsMapping());
    }

    private async Task EnsureIndexAsync<T>(
        string indexName,
        Action<CreateIndexRequestDescriptor<T>> configure) where T : class
    {
        var exists = await _elastic.Indices.ExistsAsync(indexName);
        if (exists.Exists)
        {
            _logger.LogInformation("Elasticsearch index '{Index}' already exists — skipping", indexName);
            return;
        }

        var resp = await _elastic.Indices.CreateAsync<T>(indexName, configure);
        if (!resp.IsValidResponse)
            throw new InvalidOperationException(
                $"Failed to create Elasticsearch index '{indexName}': {resp.DebugInformation}");

        _logger.LogInformation("Created Elasticsearch index '{Index}'", indexName);
    }

    private Action<CreateIndexRequestDescriptor<PatientEncounterDocument>> BuildPatientEncountersMapping() =>
        d => d.Mappings(m => m.Properties(p => p
            .Keyword(k => k.EncounterId)
            .Keyword(k => k.PatientId)
            .Keyword(k => k.Mrn)
            // text for full-text search + keyword sub-field for exact sort/filter
            .Text(t => t.PatientName, tf => tf
                .Fields(f => f.Keyword(k => k.PatientName)))
            .Keyword(k => k.Department)
            .Keyword(k => k.Status)
            .Keyword(k => k.AttendingPhysician)
            .Keyword(k => k.RoomBed!)
            .Text(t => t.AdmissionReason!)
            .IntegerNumber(i => i.News2Score!)
            .Keyword(k => k.News2RiskLevel!)
            .Date(d => d.AdmittedAt)
            .IntegerNumber(i => i.OpenAlertCount)
            .Date(d => d.LastObservationAt!)
        ));

    private Action<CreateIndexRequestDescriptor<ObservationDocument>> BuildObservationsMapping() =>
        d => d.Mappings(m => m.Properties(p => p
            .Keyword(k => k.ObservationId)
            .Keyword(k => k.EncounterId)
            .Keyword(k => k.PatientId)
            .Keyword(k => k.Mrn)
            .Keyword(k => k.ObservationCode)
            // float: observations are decimal values like 97.3, 2.5, 118.0
            // auto-mapped 'long' would truncate fractional parts silently
            .FloatNumber(f => f.Value)
            .Keyword(k => k.Unit)
            .Keyword(k => k.Source)
            .Date(d => d.RecordedAt)
        ));

    private Action<CreateIndexRequestDescriptor<ClinicalAlertDocument>> BuildClinicalAlertsMapping() =>
        d => d.Mappings(m => m.Properties(p => p
            .Keyword(k => k.AlertId)
            .Keyword(k => k.EncounterId)
            .Keyword(k => k.PatientId)
            .Keyword(k => k.Department)
            .Keyword(k => k.AlertType)
            .Keyword(k => k.Severity)
            .Keyword(k => k.Status)
            .Date(d => d.TriggeredAt)
        ));

    public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
