using System.ComponentModel.DataAnnotations;
using Asp.Versioning;
using Baya.Application.Features.Payouts.Commands.ExecutePayoutBatch;
using Baya.Application.Features.Payouts.Commands.GeneratePayoutBatch;
using Baya.Application.Features.Payouts.Commands.MarkPayoutFailed;
using Baya.Application.Features.Payouts.Commands.RetryFailedPayout;
using Baya.Application.Features.Payouts.Queries.ComputeEligibleEarnings;
using Baya.Application.Features.Payouts.Queries.GetBatchDetail;
using Baya.Application.Features.Payouts.Queries.ListPayoutBatches;
using Baya.Application.Models.Common;
using Baya.Application.Models.Payouts;
using Baya.Infrastructure.Identity.Identity.PermissionManager;
using Baya.WebFramework.Attributes;
using Baya.WebFramework.BaseController;
using Baya.WebFramework.ServiceConfiguration;
using Mediator;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;

namespace Baya.Web.Api.Controllers.V1;

/// <summary>
/// Admin payout console: preview eligible earnings, open a draft batch, submit it to the (mocked) PAYA/SATNA
/// rail, retry a failed payout, mark a reconciled bank rejection, and read batches. Generating and processing a
/// batch move real (mocked) money and are rate-limited as money endpoints. One payout per booking is guaranteed by
/// the <c>nurse_payout_booking_links.booking_id</c> UNIQUE; processing is the one irreversible step.
/// </summary>
[ApiVersion("1")]
[ApiController]
[Route("api/v{version:apiVersion}/admin_payouts")]
[Authorize(ConstantPolicies.DynamicPermission)]
[EnableRateLimiting(RateLimitingServiceExtension.SensitivePolicy)]
[Display(Description = "Admin payout batches: eligible preview, generate, process, retry, mark-failed, read")]
public sealed class AdminPayoutsController(ISender sender) : BaseController
{
    [HttpGet("eligible")]
    [ProducesOkApiResponseType<PagedResult<EligibleNurseEarningsDto>>]
    public async Task<IActionResult> Eligible([FromQuery] ComputeEligibleEarningsQuery query, CancellationToken cancellationToken)
        => OperationResult(await sender.Send(query, cancellationToken));

    [HttpPost("batches")]
    [ProducesOkApiResponseType<GeneratePayoutBatchResult>]
    public async Task<IActionResult> Generate(GeneratePayoutBatchCommand command, CancellationToken cancellationToken)
        // SystemInitiated is scheduler-only — neutralize any request-supplied value so an API caller can never
        // record a batch without an authenticated admin initiator (refinement-phase-7).
        => OperationResult(await sender.Send(command with { SystemInitiated = false }, cancellationToken));

    [HttpPost("batches/{id}/process")]
    [ProducesOkApiResponseType<ExecutePayoutBatchResult>]
    public async Task<IActionResult> Process(long id, CancellationToken cancellationToken)
        => OperationResult(await sender.Send(new ExecutePayoutBatchCommand(id), cancellationToken));

    [HttpGet("batches/{id}")]
    [ProducesOkApiResponseType<PayoutBatchDetailDto>]
    public async Task<IActionResult> Get(long id, [FromQuery] int page = 1, [FromQuery] int pageSize = 50, CancellationToken cancellationToken = default)
        => OperationResult(await sender.Send(new GetBatchDetailQuery(id, page, pageSize), cancellationToken));

    [HttpGet("batches")]
    [ProducesOkApiResponseType<PagedResult<PayoutBatchDto>>]
    public async Task<IActionResult> List([FromQuery] ListPayoutBatchesQuery query, CancellationToken cancellationToken)
        => OperationResult(await sender.Send(query, cancellationToken));

    [HttpPost("{payoutId}/retry")]
    [ProducesOkApiResponseType<bool>]
    public async Task<IActionResult> Retry(long payoutId, CancellationToken cancellationToken)
        => OperationResult(await sender.Send(new RetryFailedPayoutCommand(payoutId), cancellationToken));

    [HttpPost("{payoutId}/mark_failed")]
    [ProducesOkApiResponseType<bool>]
    public async Task<IActionResult> MarkFailed(long payoutId, MarkPayoutFailedBody body, CancellationToken cancellationToken)
        => OperationResult(await sender.Send(new MarkPayoutFailedCommand(payoutId, body.FailureReason), cancellationToken));

    /// <summary>The mark-failed body (the payout id comes from the route).</summary>
    public record MarkPayoutFailedBody(string FailureReason);
}
