using System.Net.Http.Headers;
using System.Net.Http.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using StackExchange.Redis;

public sealed class EncounterReplicaSyncService : BackgroundService
{
    private const int EncounterPageSize = 100;

    private readonly IServiceScopeFactory _scopes;
    private readonly IHttpClientFactory _http;
    private readonly CentralReachabilityService _reachability;
    private readonly GatewayOptions _gateway;
    private readonly ILogger<EncounterReplicaSyncService> _logger;

    public EncounterReplicaSyncService(
        IServiceScopeFactory scopes,
        IHttpClientFactory http,
        CentralReachabilityService reachability,
        IOptions<GatewayOptions> gateway,
        ILogger<EncounterReplicaSyncService> logger)
    {
        _scopes = scopes;
        _http = http;
        _reachability = reachability;
        _gateway = gateway.Value;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        await SyncOnceAsync(ct);

        using var timer = new PeriodicTimer(
            TimeSpan.FromMinutes(_gateway.EncounterSyncIntervalMinutes));
        while (await timer.WaitForNextTickAsync(ct))
        {
            if (_reachability.IsCentralReachable)
                await SyncOnceAsync(ct);
        }
    }

    internal async Task SyncOnceAsync(CancellationToken ct)
    {
        await using var scope = _scopes.CreateAsyncScope();
        var db = scope.ServiceProvider.GetRequiredService<GatewayDbContext>();
        var redis = scope.ServiceProvider.GetRequiredService<IConnectionMultiplexer>();
        var centralOpts = scope.ServiceProvider.GetRequiredService<IOptions<CentralApiOptions>>().Value;

        var client = _http.CreateClient("central");
        client.BaseAddress = new Uri(centralOpts.BaseUrl);
        ConfigureAuth(client);

        if (client.DefaultRequestHeaders.Authorization is null)
        {
            _logger.LogWarning(
                "GATEWAY_SYNC_JWT not set — skipping encounter replica sync (seed local replica manually for dev)");
            return;
        }

        try
        {
            var syncedCount = await SyncEncountersAsync(client, db, ct);
            await SyncThresholdsAsync(client, db, redis, ct);

            var state = await db.SyncState.FirstOrDefaultAsync(ct)
                ?? db.SyncState.Add(new GatewaySyncState { Id = Guid.NewGuid() }).Entity;
            state.LastEncounterSyncAt = DateTimeOffset.UtcNow;
            state.InitialSyncCompleted = true;
            await db.SaveChangesAsync(ct);

            _logger.LogInformation("Encounter replica sync completed — {Count} encounters", syncedCount);
        }
        catch (HttpRequestException ex)
        {
            _logger.LogWarning(ex, "Encounter replica sync failed — central API unreachable");
        }
    }

    private async Task<int> SyncEncountersAsync(
        HttpClient client, GatewayDbContext db, CancellationToken ct)
    {
        var dept = Uri.EscapeDataString(_gateway.Department);
        var summaries = new List<CentralWardEncounterSummary>();
        var page = 1;

        while (true)
        {
            var listResp = await client.GetFromJsonAsync<ApiResponse<CentralEncounterListPage>>(
                $"/api/v1/encounters?status=ACTIVE&department={dept}&page={page}&pageSize={EncounterPageSize}",
                CentralApiJson.Options,
                ct);

            if (listResp?.Data?.Items is null || listResp.Data.Items.Count == 0)
                break;

            summaries.AddRange(listResp.Data.Items);

            if (page >= listResp.Data.TotalPages)
                break;

            page++;
        }

        var synced = 0;
        foreach (var summary in summaries)
        {
            var detailResp = await client.GetFromJsonAsync<ApiResponse<CentralEncounterDetail>>(
                $"/api/v1/encounters/{summary.EncounterId}",
                CentralApiJson.Options,
                ct);

            if (detailResp?.Data is null)
            {
                _logger.LogWarning("Central returned no detail for encounter {EncounterId}", summary.EncounterId);
                continue;
            }

            UpsertReplica(db, detailResp.Data);
            synced++;
        }

        return synced;
    }

    private static void UpsertReplica(GatewayDbContext db, CentralEncounterDetail detail)
    {
        var now = DateTimeOffset.UtcNow;
        var patient = detail.Patient;

        var replicaPatient = db.Patients.Find(patient.Id);
        if (replicaPatient is null)
        {
            db.Patients.Add(new ReplicaPatient
            {
                Id = patient.Id,
                Mrn = patient.Mrn,
                FirstName = patient.FirstName,
                LastName = patient.LastName,
                DateOfBirth = patient.DateOfBirth,
                Gender = patient.Gender,
                SyncedAt = now
            });
        }
        else
        {
            replicaPatient.Mrn = patient.Mrn;
            replicaPatient.FirstName = patient.FirstName;
            replicaPatient.LastName = patient.LastName;
            replicaPatient.DateOfBirth = patient.DateOfBirth;
            replicaPatient.Gender = patient.Gender;
            replicaPatient.SyncedAt = now;
        }

        var enc = db.Encounters.Find(detail.Id);
        if (enc is null)
        {
            db.Encounters.Add(new ReplicaEncounter
            {
                Id = detail.Id,
                PatientId = detail.PatientId,
                EncounterType = detail.EncounterType,
                Status = detail.Status,
                Department = detail.Department,
                AttendingPhysician = detail.AttendingPhysician,
                RoomBed = detail.RoomBed,
                AdmittedAt = detail.AdmittedAt,
                SyncedAt = now
            });
        }
        else
        {
            enc.PatientId = detail.PatientId;
            enc.EncounterType = detail.EncounterType;
            enc.Status = detail.Status;
            enc.Department = detail.Department;
            enc.AttendingPhysician = detail.AttendingPhysician;
            enc.RoomBed = detail.RoomBed;
            enc.AdmittedAt = detail.AdmittedAt;
            enc.SyncedAt = now;
        }
    }

    private async Task SyncThresholdsAsync(
        HttpClient client,
        GatewayDbContext db,
        IConnectionMultiplexer redis,
        CancellationToken ct)
    {
        var thrResp = await client.GetFromJsonAsync<ApiResponse<List<CentralAlertThreshold>>>(
            "/api/v1/alert-thresholds",
            CentralApiJson.Options,
            ct);

        if (thrResp?.Data is null)
            return;

        await db.Database.ExecuteSqlRawAsync("DELETE FROM alert_thresholds", ct);
        var syncedAt = DateTimeOffset.UtcNow;
        db.AlertThresholds.AddRange(thrResp.Data.Select(t => new ReplicaAlertThreshold
        {
            Id = t.Id,
            ObservationCode = t.ObservationCode,
            DisplayName = t.DisplayName,
            Unit = t.Unit,
            CriticalLow = t.CriticalLow,
            WarningLow = t.WarningLow,
            WarningHigh = t.WarningHigh,
            CriticalHigh = t.CriticalHigh,
            SuppressionWindowMinutes = t.SuppressionWindowMinutes,
            SyncedAt = syncedAt
        }));

        await db.SaveChangesAsync(ct);
        await ThresholdCacheLoader.RefreshAsync(db, redis, _logger, ct);
    }

    private static void ConfigureAuth(HttpClient client)
    {
        client.DefaultRequestHeaders.Authorization = null;

        var jwt = Environment.GetEnvironmentVariable("GATEWAY_SYNC_JWT");
        if (!string.IsNullOrEmpty(jwt))
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", jwt);
    }
}
