using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using ROLAC.API.Authorization;
using ROLAC.API.DTOs.Expense;
using ROLAC.API.Services;

namespace ROLAC.API.Controllers;

// Class is [Authorize] only — any authenticated member may submit/view their OWN
// reimbursements. Finance-level privileges (view-all, edit-any, approve) are resolved
// against the configurable permission matrix on the "Expenses" module.
[ApiController]
[Route("api/expenses")]
[Authorize]
public class ExpensesController : ControllerBase
{
    private readonly IExpenseService    _svc;
    private readonly IPermissionService _perms;
    public ExpensesController(IExpenseService svc, IPermissionService perms)
    {
        _svc   = svc;
        _perms = perms;
    }

    private List<string> Roles() => User.FindAll("role").Select(claim => claim.Value).ToList();
    private bool IsSuperAdmin() => User.IsInRole(PermissionAuthorizationHandler.SuperAdminRole);

    // Can manage any expense (edit/delete/upload on others' records). Maps to Expenses:Write.
    private async Task<bool> CanManageAsync() =>
        IsSuperAdmin() || await _perms.HasPermissionAsync(Roles(), Modules.Expenses, PermissionActions.Write);

    // Can view all expenses (not just own). Maps to Expenses:Read (finance + pastor by default).
    private async Task<bool> CanViewAllAsync() =>
        IsSuperAdmin() || await _perms.HasPermissionAsync(Roles(), Modules.Expenses, PermissionActions.Read);

    // User id lives in the "sub" claim (NameClaimType="sub"); NameIdentifier is absent at runtime.
    private string CurrentUserId() =>
        User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub") ?? "";

    [HttpGet]
    public async Task<IActionResult> GetPaged(
        [FromQuery] int page = 1, [FromQuery] int pageSize = 20, [FromQuery] string? search = null,
        [FromQuery] int? ministryId = null, [FromQuery] int? categoryGroupId = null,
        [FromQuery] string? status = null, [FromQuery] DateOnly? from = null, [FromQuery] DateOnly? to = null,
        [FromQuery] int? subCategoryId = null, [FromQuery] string? statuses = null)
    {
        if (!await CanViewAllAsync()) return Forbid();
        return Ok(await _svc.GetPagedAsync(page, pageSize, search, ministryId, categoryGroupId, status, from, to, subCategoryId, statuses));
    }

    [HttpGet("mine")]
    public async Task<IActionResult> GetMine([FromQuery] string? status = null, [FromQuery] int page = 1, [FromQuery] int pageSize = 20)
    {
        return Ok(await _svc.GetMineAsync(CurrentUserId(), status, page, pageSize));
    }

    [HttpGet("{id:int}")]
    public async Task<IActionResult> GetById(int id)
    {
        var dto = await _svc.GetByIdAsync(id);
        if (dto is null) return NotFound();
        if (!await CanViewAllAsync() && dto.SubmittedBy != CurrentUserId()) return Forbid();
        return Ok(dto);
    }

    [HttpPost]
    public async Task<IActionResult> Create([FromBody] CreateExpenseRequest r)
    {
        try { return Ok(new { id = await _svc.CreateAsync(r, await CanManageAsync()) }); }
        catch (InvalidOperationException ex) { return Conflict(new { message = ex.Message }); }
    }

    [HttpPut("{id:int}")]
    public async Task<IActionResult> Update(int id, [FromBody] UpdateExpenseRequest r)
    {
        try { await _svc.UpdateAsync(id, r, await CanManageAsync()); return NoContent(); }
        catch (KeyNotFoundException) { return NotFound(); }
        catch (InvalidOperationException ex) { return Conflict(new { message = ex.Message }); }
    }

    [HttpDelete("{id:int}")]
    public async Task<IActionResult> Delete(int id)
    {
        try { await _svc.DeleteAsync(id, await CanManageAsync()); return NoContent(); }
        catch (KeyNotFoundException) { return NotFound(); }
        catch (InvalidOperationException ex) { return Conflict(new { message = ex.Message }); }
    }

    [HttpPost("{id:int}/submit")]
    public async Task<IActionResult> Submit(int id)
    {
        try { await _svc.SubmitAsync(id); return NoContent(); }
        catch (KeyNotFoundException) { return NotFound(); }
        catch (InvalidOperationException ex) { return Conflict(new { message = ex.Message }); }
    }

    [HttpPost("{id:int}/approve")]
    [HasPermission(Modules.Expenses, PermissionActions.Approve)]
    public async Task<IActionResult> Approve(int id)
    {
        try { await _svc.ApproveAsync(id); return NoContent(); }
        catch (KeyNotFoundException) { return NotFound(); }
        catch (InvalidOperationException ex) { return Conflict(new { message = ex.Message }); }
    }

    [HttpPost("{id:int}/reject")]
    [HasPermission(Modules.Expenses, PermissionActions.Approve)]
    public async Task<IActionResult> Reject(int id, [FromBody] RejectExpenseRequest r)
    {
        try { await _svc.RejectAsync(id, r.ReviewNotes); return NoContent(); }
        catch (KeyNotFoundException) { return NotFound(); }
        catch (InvalidOperationException ex) { return Conflict(new { message = ex.Message }); }
    }

    [HttpPost("{id:int}/pay")]
    [HasPermission(Modules.Expenses, PermissionActions.Approve)]
    public async Task<IActionResult> Pay(int id, [FromBody] PayExpenseRequest r)
    {
        try { await _svc.PayAsync(id, r.CheckNumber, r.PaidAt); return NoContent(); }
        catch (KeyNotFoundException) { return NotFound(); }
        catch (InvalidOperationException ex) { return Conflict(new { message = ex.Message }); }
    }

    [HttpPost("{id:int}/receipt")]
    [RequestSizeLimit(10_485_760)]
    public async Task<IActionResult> UploadReceipt(int id, IFormFile file)
    {
        if (file is null || file.Length == 0) return BadRequest(new { message = "No file." });
        var allowed = new[] { "image/jpeg", "image/png", "image/webp", "application/pdf" };
        if (!allowed.Contains(file.ContentType)) return BadRequest(new { message = "Unsupported file type." });
        try
        {
            await using var stream = file.OpenReadStream();
            await _svc.SaveReceiptAsync(id, stream, file.FileName, await CanManageAsync());
            return NoContent();
        }
        catch (KeyNotFoundException) { return NotFound(); }
        catch (InvalidOperationException ex) { return Conflict(new { message = ex.Message }); }
    }

    [HttpGet("{id:int}/receipt")]
    public async Task<IActionResult> GetReceipt(int id)
    {
        try
        {
            var result = await _svc.OpenReceiptAsync(id, await CanManageAsync());
            if (result is null) return NotFound();
            return File(result.Value.stream, result.Value.contentType);
        }
        catch (KeyNotFoundException) { return NotFound(); }
        catch (InvalidOperationException) { return Forbid(); }
    }
}
