
using System.Security.Claims;
using System.Text.Json;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;

/// <summary>
/// Approval and promotion of verified digitization batches to VigilCareClinical live tables.
/// </summary>
[ApiController]
[Route("api/v1/digitization-batches")]
[Produces("application/json")]
[Authorize]
public class ApprovalController : ControllerBase
{
    private readonly IPromotionService _promotion;
    private readonly AppDbContext _db;
    private readonly PromotionRetryOptions _retryOptions;
    private readonly ILogger<ApprovalController> _logger;

    public ApprovalController(
        IPromotionService promotion,
        AppDbContext db,
        IOptions<PromotionRetryOptions> retryOptions,
        ILogger<ApprovalController> logger)
    {
        _promotion = promotion;
        _db = db;
        _retryOptions = retryOptions.Value;
        _logger = logger;
    }

    /// <summary>
    /// Approves a verified or awaiting-clinical-approval batch and promotes its draft data
    /// into live VigilCareClinical tables (Patient, Encounter, Observations).
    ///
    /// Requires the Idempotency-Key header for safe retries. If the same key is resubmitted,
    /// the original response is returned without re-executing the promotion.
    ///
    /// If promotion fails due to infrastructure issues, the batch transitions to Approved status
    /// and automatic retry is scheduled. Returns 202 Accepted with PROMOTION_DEFERRED.
    ///
    /// Separation of duties: the approver cannot be the entry clerk or verifier of the same batch.
    /// </summary>
    /// <param name="id">The batch ID to approve.</param>
    /// <param name="request">Optional request body with alert configuration.</param>
    /// <returns>Promotion result with live IDs for patient, encounter, and observations.</returns>
    [HttpPost("{id:guid}/approve")]
    [Authorize(Roles = "CLINICAL_APPROVER,ADMINISTRATOR")]
    [ProducesResponseType(typeof(ApiResponse<PromotionResultResponse>), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status202Accepted)]
    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status400BadRequest)]
    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status409Conflict)]
    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status422UnprocessableEntity)]
    public async Task<IActionResult> Approve(Guid id, [FromBody] ApproveRequest? request)
    {
        var idempotencyKey = Request.Headers["Idempotency-Key"].FirstOrDefault();

        if (string.IsNullOrWhiteSpace(idempotencyKey))
            return BadRequest(ApiResponse<object>.Fail(400,
                "Idempotency-Key header is required for promotion operations.",
                "MISSING_IDEMPOTENCY_KEY"));

        if (idempotencyKey.Length > 100)
            return BadRequest(ApiResponse<object>.Fail(400,
                "Idempotency-Key must be at most 100 characters.",
                "INVALID_IDEMPOTENCY_KEY"));

        var approverUserId = Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
        var enableRetroactiveAlerts = request?.EnableRetroactiveAlerts ?? false;

        _logger.LogInformation(
            "Approve request for batch {BatchId} by user {UserId} with idempotency key {Key}",
            id, approverUserId, idempotencyKey);

        try
        {
            var result = await _promotion.ApproveAndPromoteAsync(
                id, approverUserId, enableRetroactiveAlerts, idempotencyKey);

            return Ok(ApiResponse<PromotionResultResponse>.Ok(result));
        }
        catch (Exception ex) when (ex is not NotFoundException
                                && ex is not ConflictException
                                && ex is not ValidationException)
        {
            _logger.LogWarning(ex,
                "Promotion failed for batch {BatchId}, scheduling for retry", id);

            return await DeferPromotionAsync(id, approverUserId, enableRetroactiveAlerts, ex);
        }
    }

    /// <summary>
    /// Returns the promotion result for an already-promoted batch, including the live IDs
    /// for Patient, Encounter, and Observations that were created during promotion.
    /// </summary>
    /// <param name="id">The batch ID to query.</param>
    /// <returns>Promotion result with live clinical entity IDs.</returns>
    [HttpGet("{id:guid}/promotion-result")]
    [Authorize]
    [ProducesResponseType(typeof(ApiResponse<PromotionResultResponse>), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status409Conflict)]
    public async Task<IActionResult> GetPromotionResult(Guid id)
    {
        var result = await _promotion.GetPromotionResultAsync(id);
        return Ok(ApiResponse<PromotionResultResponse>.Ok(result));
    }

    private async Task<IActionResult> DeferPromotionAsync(
        Guid batchId,
        Guid approverUserId,
        bool enableRetroactiveAlerts,
        Exception ex)
    {
        var batch = await _db.DigitizationBatches.FindAsync(batchId);
        if (batch is null)
            throw new NotFoundException("Batch not found.", "BATCH_NOT_FOUND");

        if (batch.Status is not (BatchStatus.Verified or BatchStatus.AwaitingClinicalApproval))
        {
            throw new ConflictException(
                $"Cannot defer promotion for batch in '{batch.Status.ToDbString()}' status.",
                "ILLEGAL_STATUS_TRANSITION");
        }

        var now = DateTimeOffset.UtcNow;
        batch.Status = BatchStatus.Approved;
        batch.ApprovedByUserId = approverUserId;
        batch.EnableRetroactiveAlerts = enableRetroactiveAlerts;
        batch.UpdatedAt = now;

        _db.DigitizationEvents.Add(new DigitizationEvent
        {
            Id = Guid.NewGuid(),
            BatchId = batchId,
            EventType = DigitizationEventType.Approved,
            ActorUserId = approverUserId,
            OccurredAt = now,
            MetadataJson = JsonSerializer.Serialize(new
            {
                enableRetroactiveAlerts,
                promotionDeferred = true
            })
        });

        var nextRetryAt = now.Add(TimeSpan.FromSeconds(_retryOptions.InitialDelaySeconds));
        var attempt = new PromotionAttempt
        {
            Id = Guid.NewGuid(),
            BatchId = batchId,
            AttemptNumber = 1,
            Succeeded = false,
            ErrorMessage = ex.Message.Length > 2000
                ? ex.Message[..2000]
                : ex.Message,
            AttemptedAt = now,
            NextRetryAt = nextRetryAt
        };

        _db.PromotionAttempts.Add(attempt);

        _db.DigitizationEvents.Add(new DigitizationEvent
        {
            Id = Guid.NewGuid(),
            BatchId = batchId,
            EventType = DigitizationEventType.PromotionFailed,
            ActorUserId = approverUserId,
            OccurredAt = now,
            MetadataJson = JsonSerializer.Serialize(new
            {
                error = ex.Message,
                nextRetryAt = nextRetryAt.ToString("o"),
                scheduledForRetry = true
            })
        });

        await _db.SaveChangesAsync();

        return StatusCode(202, ApiResponse<object>.Fail(
            202,
            "Batch approved but promotion deferred due to infrastructure issue. " +
            "Automatic retry has been scheduled.",
            "PROMOTION_DEFERRED"));
    }
}
