using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;

public class AlertService : IAlertService
{
    private readonly AppDbContext _db;
    private readonly IServiceProvider _services;
    private readonly ICurrentUserService _currentUser;
    private readonly IAuditService _audit;

    public AlertService(
        AppDbContext db,
        IServiceProvider services,
        ICurrentUserService currentUser,
        IAuditService audit)
    {
        _db = db;
        _services = services;
        _currentUser = currentUser;
        _audit = audit;
    }

    public async Task<PagedResult<AlertResponse>> ListByEncounterAsync(
        Guid encounterId, AlertStatus? status, int page, int pageSize)
    {
        var query = _db.ClinicalAlerts
            .AsNoTracking()
            .Where(a => a.EncounterId == encounterId);

        if (status.HasValue)
            query = query.Where(a => a.Status == status.Value);

        var total = await query.CountAsync();
        var alerts = await query
            .OrderByDescending(a => a.TriggeredAt)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync();

        return new PagedResult<AlertResponse>(
            alerts.Select(AlertResponseMapper.ToResponse).ToList(),
            page, pageSize, total);
    }

    public async Task<PagedResult<AlertResponse>> ListGlobalAsync(
        AlertStatus? status, AlertSeverity? severity, Department? department, int page, int pageSize)
    {
        var query = _db.ClinicalAlerts
            .AsNoTracking()
            .Include(a => a.Encounter)
            .AsQueryable();

        if (status.HasValue)
            query = query.Where(a => a.Status == status.Value);

        if (severity.HasValue)
            query = query.Where(a => a.Severity == severity.Value);

        if (department.HasValue)
            query = query.Where(a => a.Encounter.Department == department.Value);

        var total = await query.CountAsync();
        var alerts = await query
            .OrderByDescending(a => a.TriggeredAt)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync();

        return new PagedResult<AlertResponse>(
            alerts.Select(AlertResponseMapper.ToResponse).ToList(),
            page, pageSize, total);
    }

    public async Task<AlertResponse> GetByIdAsync(Guid id)
    {
        var alert = await _db.ClinicalAlerts
            .AsNoTracking()
            .Include(a => a.Encounter)
            .FirstOrDefaultAsync(a => a.Id == id);

        if (alert is null)
            throw new NotFoundException("Alert not found.", "ALERT_NOT_FOUND");

        return AlertResponseMapper.ToResponse(alert);
    }

    public async Task<AlertResponse> AcknowledgeAsync(Guid id, AcknowledgeAlertRequest req)
    {
        if (!_currentUser.IsAuthenticated)
            throw new ValidationException("Authentication required.", "AUTH_REQUIRED");

        var displayName = _currentUser.DisplayName ?? _currentUser.Username
            ?? throw new ValidationException("Authenticated user identity missing.", "AUTH_REQUIRED");

        var alert = await _db.ClinicalAlerts.FindAsync(id);
        if (alert is null)
            throw new NotFoundException("Alert not found.", "ALERT_NOT_FOUND");

        if (alert.Status != AlertStatus.Open && alert.Status != AlertStatus.Escalated)
            throw new ConflictException(
                $"Alert cannot be acknowledged from status '{alert.Status}'.",
                "ALERT_NOT_ACKNOWLEDGEABLE");

        var roleLabel = _currentUser.Role?.ToDbString() ?? "UNKNOWN";
        var userId = _currentUser.UserId;
        var acknowledgmentNote = FormatAcknowledgmentNote(roleLabel, displayName, req.Note);

        var previousStatus = alert.Status;
        alert.Status = AlertStatus.Acknowledged;
        alert.AcknowledgedAt = DateTimeOffset.UtcNow;
        alert.AcknowledgedBy = $"{displayName} ({roleLabel})";

        // Write an outbox event so the Kafka consumer (Phase 6) can cancel the
        // pending RabbitMQ escalation timer when it sees this acknowledgment.
        _db.OutboxEvents.Add(new OutboxEvent
        {
            Id = Guid.NewGuid(),
            Topic = "alert.acknowledged",
            Payload = JsonSerializer.Serialize(new
            {
                alertId = alert.Id,
                encounterId = alert.EncounterId,
                acknowledgedBy = alert.AcknowledgedBy,
                userId,
                role = roleLabel,
                acknowledgedAt = alert.AcknowledgedAt,
                note = acknowledgmentNote
            }),
            PartitionKey = alert.EncounterId.ToString(),
            CreatedAt = DateTimeOffset.UtcNow
        });

        if (alert.AlertType.IsSuppressible())
        {
            var suppression = _services.GetRequiredService<IAlertSuppressionService>();
            var options = _services.GetRequiredService<IOptions<SuppressionOptions>>().Value;
            var windowMinutes = await ResolveSuppressionWindowMinutesAsync(
                alert.AlertType, options.DefaultWindowMinutes);

            await suppression.SetSuppressionAsync(
                alert.EncounterId,
                alert.AlertType,
                TimeSpan.FromMinutes(windowMinutes));
        }

        await _db.SaveChangesAsync();

        await _audit.WriteAsync(
            AuditAction.AlertAcknowledged,
            "ClinicalAlert",
            alert.Id,
            previousValue: new { status = previousStatus.ToDbString() },
            newValue: new { status = alert.Status.ToDbString(), alert.AcknowledgedBy, role = roleLabel, userId },
            reason: acknowledgmentNote);

        if (alert.AlertType.IsSuppressible())
        {
            await _audit.WriteAsync(
                AuditAction.SuppressionWindowSet,
                "ClinicalAlert",
                alert.Id,
                newValue: new { alert.AlertType, alert.EncounterId },
                reason: acknowledgmentNote);
        }

        return AlertResponseMapper.ToResponse(alert);
    }

    public async Task<AlertFeedback> SubmitFeedbackAsync(
        Guid alertId, AlertFeedbackType type, string? comment)
    {
        if (!_currentUser.IsAuthenticated || _currentUser.UserId is null)
            throw new ValidationException("Authentication required.", "AUTH_REQUIRED");

        var userId = _currentUser.UserId.Value;

        var alert = await _db.ClinicalAlerts.FindAsync(alertId);
        if (alert is null)
            throw new NotFoundException("Alert not found.", "ALERT_NOT_FOUND");

        if (alert.Status == AlertStatus.Open)
            throw new BadRequestException(
                "Feedback can only be submitted on acknowledged or resolved alerts.",
                "ALERT_NOT_REVIEWABLE");

        var alreadySubmitted = await _db.AlertFeedbacks
            .AnyAsync(f => f.AlertId == alertId && f.UserId == userId);
        if (alreadySubmitted)
            throw new ConflictException(
                "You have already submitted feedback for this alert.",
                "FEEDBACK_ALREADY_SUBMITTED");

        var feedback = new AlertFeedback
        {
            Id = Guid.NewGuid(),
            AlertId = alertId,
            UserId = userId,
            FeedbackType = type,
            Comment = string.IsNullOrWhiteSpace(comment) ? null : comment.Trim(),
            CreatedAt = DateTimeOffset.UtcNow
        };

        _db.AlertFeedbacks.Add(feedback);
        alert.FeedbackReceived = true;
        await _db.SaveChangesAsync();

        await _audit.WriteAsync(
            AuditAction.AlertFeedbackSubmitted,
            "ClinicalAlert",
            alert.Id,
            newValue: new { feedbackType = type.ToDbString(), feedback.UserId },
            reason: comment);

        return feedback;
    }

    private async Task<int> ResolveSuppressionWindowMinutesAsync(
        AlertType alertType, int defaultWindowMinutes)
    {
        if (alertType == AlertType.News2Warning)
            return defaultWindowMinutes;

        var observationCode = alertType.ObservationCodeForWarning();
        if (observationCode is null)
            return defaultWindowMinutes;

        var overrideMinutes = await _db.AlertThresholds
            .AsNoTracking()
            .Where(t => t.ObservationCode == observationCode)
            .Select(t => t.SuppressionWindowMinutes)
            .FirstOrDefaultAsync();

        return overrideMinutes ?? defaultWindowMinutes;
    }

    public async Task<AlertResponse> ResolveAsync(Guid id)
    {
        var alert = await _db.ClinicalAlerts.FindAsync(id);
        if (alert is null)
            throw new NotFoundException("Alert not found.", "ALERT_NOT_FOUND");

        if (alert.Status != AlertStatus.Acknowledged)
            throw new ConflictException(
                "Alert must be acknowledged before it can be resolved.",
                "ALERT_NOT_ACKNOWLEDGED");

        alert.Status = AlertStatus.Resolved;
        alert.ResolvedAt = DateTimeOffset.UtcNow;
        await _db.SaveChangesAsync();

        await _audit.WriteAsync(
            AuditAction.AlertResolved,
            "ClinicalAlert",
            alert.Id,
            previousValue: new { status = AlertStatus.Acknowledged.ToDbString() },
            newValue: new { status = alert.Status.ToDbString() });

        return AlertResponseMapper.ToResponse(alert);
    }

    public async Task ApplySyncedAcknowledgmentAsync(
        Guid alertId, SyncedAlertAcknowledgment ack, CancellationToken ct)
    {
        var alert = await _db.ClinicalAlerts.FindAsync([alertId], ct)
            ?? throw new NotFoundException("Alert not found.", "ALERT_NOT_FOUND");

        alert.Status = AlertStatus.Acknowledged;
        alert.AcknowledgedAt = ack.AcknowledgedAt;
        alert.AcknowledgedBy = ack.ClinicianId;

        _db.OutboxEvents.Add(new OutboxEvent
        {
            Id = Guid.NewGuid(),
            Topic = "alert.acknowledged",
            Payload = JsonSerializer.Serialize(new
            {
                alertId = alert.Id,
                encounterId = alert.EncounterId,
                acknowledgedBy = ack.ClinicianId,
                acknowledgedAt = ack.AcknowledgedAt,
                note = ack.Note,
                syncedFromGateway = true
            }),
            PartitionKey = alert.EncounterId.ToString(),
            CreatedAt = DateTimeOffset.UtcNow
        });

        await _db.SaveChangesAsync(ct);
    }

    public async Task ApplySyncedResolutionAsync(
        Guid alertId, SyncedAlertResolution resolve, CancellationToken ct)
    {
        var alert = await _db.ClinicalAlerts.FindAsync([alertId], ct)
            ?? throw new NotFoundException("Alert not found.", "ALERT_NOT_FOUND");

        if (alert.Status != AlertStatus.Acknowledged)
            alert.Status = AlertStatus.Acknowledged;

        alert.Status = AlertStatus.Resolved;
        alert.ResolvedAt = resolve.ResolvedAt;
        await _db.SaveChangesAsync(ct);
    }

    private static string FormatAcknowledgmentNote(string roleLabel, string displayName, string? note)
    {
        var prefix = $"[{roleLabel}] Acknowledged by {displayName}.";
        return string.IsNullOrWhiteSpace(note) ? prefix : $"{prefix} {note.Trim()}";
    }

    public static class AlertResponseMapper
    {
        public static AlertResponse ToResponse(ClinicalAlert alert) => new(
            alert.Id,
            alert.EncounterId,
            alert.PatientId,
            alert.AlertType,
            alert.Severity,
            alert.Details,
            alert.Status,
            alert.TriggeredAt,
            alert.AcknowledgedAt,
            alert.AcknowledgedBy,
            alert.ResolvedAt,
            alert.Explanation);
    }
}