using ERPCore.Domain.Enums;
using ERPCore.Dtos.Common;
using ERPCore.Dtos.Hrm;
using ERPCore.Infra.Auth;
using ERPCore.Services.Interfaces;
using Microsoft.AspNetCore.Mvc;

namespace ERPCore.Controllers.Hrm;

/// <summary>
/// Employee (staff) endpoints, incl. the Employee&lt;-&gt;User cross-link and staff
/// document sub-resources (docs/13-BACKEND-HRM-API.md §3).
/// </summary>
[Route("api/v1/employees")]
public sealed class EmployeesController : ApiControllerBase
{
    private readonly IEmployeeService _employees;
    private readonly IEmployeeUserLinkService _links;
    private readonly IEmployeeDocumentService _documents;
    private readonly ILeaveBalanceService _leaveBalances;
    private readonly IEmployeeSalaryStructureService _salaryStructures;
    private readonly IEmployeeLoanService _loans;
    private readonly ICurrentUser _currentUser;

    public EmployeesController(
        IEmployeeService employees, IEmployeeUserLinkService links, IEmployeeDocumentService documents,
        ILeaveBalanceService leaveBalances, IEmployeeSalaryStructureService salaryStructures,
        IEmployeeLoanService loans, ICurrentUser currentUser)
    {
        _employees = employees;
        _links = links;
        _documents = documents;
        _leaveBalances = leaveBalances;
        _salaryStructures = salaryStructures;
        _loans = loans;
        _currentUser = currentUser;
    }

    [HttpGet]
    [ProducesResponseType(typeof(PagedResponse<EmployeeListItemDto>), StatusCodes.Status200OK)]
    public async Task<ActionResult<PagedResponse<EmployeeListItemDto>>> List(
        [FromQuery] PageQuery query, [FromQuery] EmployeeStatus? status,
        [FromQuery] int? departmentId, [FromQuery] int? designationId, [FromQuery] int? branchId, CancellationToken ct)
        => Ok(await _employees.ListAsync(query, status, departmentId, designationId, branchId, ct));

    /// <summary>Advisory reverse-direction lookup: does a System User already exist with this email? (docs/12-BACKEND-HRM.md A.5)</summary>
    [HttpGet("email-lookup")]
    [ProducesResponseType(typeof(UserMatchResponse), StatusCodes.Status200OK)]
    public async Task<ActionResult<UserMatchResponse>> EmailLookup([FromQuery] string email, CancellationToken ct)
        => Ok(new UserMatchResponse(await _links.FindUserCandidateByEmailAsync(email, ct)));

    [HttpGet("{employeeId:int}")]
    [ProducesResponseType(typeof(EmployeeDetailDto), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<ActionResult<EmployeeDetailDto>> GetById(int employeeId, CancellationToken ct)
    {
        var result = await _employees.GetAsync(employeeId, ct);
        if (result is null) return NotFound();
        SetETag(result.RowVersion);
        return Ok(result.Value);
    }

    [HttpPost]
    [ProducesResponseType(typeof(EmployeeDetailDto), StatusCodes.Status201Created)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [ProducesResponseType(StatusCodes.Status409Conflict)]
    public async Task<ActionResult<EmployeeDetailDto>> Create([FromBody] CreateEmployeeRequest request, CancellationToken ct)
    {
        var result = await _employees.CreateAsync(request, _currentUser.AuditUserId, ct);
        SetETag(result.RowVersion);
        return Created($"/api/v1/employees/{result.Value.EmployeeId}", result.Value);
    }

    [HttpPut("{employeeId:int}")]
    [ProducesResponseType(typeof(EmployeeDetailDto), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    [ProducesResponseType(StatusCodes.Status412PreconditionFailed)]
    public async Task<ActionResult<EmployeeDetailDto>> Update(int employeeId, [FromBody] UpdateEmployeeRequest request, CancellationToken ct)
    {
        var expected = RequireIfMatch();
        var result = await _employees.UpdateAsync(employeeId, request, expected, _currentUser.AuditUserId, ct);
        SetETag(result.RowVersion);
        return Ok(result.Value);
    }

    /// <summary>Never a hard delete — Employee is retained forever (docs/12-BACKEND-HRM.md C.2).</summary>
    [HttpPatch("{employeeId:int}/status")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> SetStatus(int employeeId, [FromBody] UpdateEmployeeStatusRequest request, CancellationToken ct)
    {
        await _employees.SetStatusAsync(employeeId, request.Status, ct);
        return NoContent();
    }

    [HttpPost("{employeeId:int}/link-user")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    [ProducesResponseType(StatusCodes.Status409Conflict)]
    public async Task<IActionResult> LinkUser(int employeeId, [FromBody] LinkUserRequest request, CancellationToken ct)
    {
        await _links.LinkAsync(employeeId, request.UserId, ct);
        return NoContent();
    }

    [HttpDelete("{employeeId:int}/link-user")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> UnlinkUser(int employeeId, CancellationToken ct)
    {
        await _links.UnlinkAsync(employeeId, ct);
        return NoContent();
    }

    [HttpGet("{employeeId:int}/bank-details")]
    [ProducesResponseType(typeof(List<EmployeeBankDetailDto>), StatusCodes.Status200OK)]
    public async Task<ActionResult<List<EmployeeBankDetailDto>>> ListBankDetails(int employeeId, CancellationToken ct)
        => Ok(await _employees.ListBankDetailsAsync(employeeId, ct));

    [HttpPut("{employeeId:int}/bank-details")]
    [ProducesResponseType(typeof(List<EmployeeBankDetailDto>), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
    public async Task<ActionResult<List<EmployeeBankDetailDto>>> ReplaceBankDetails(
        int employeeId, [FromBody] ReplaceEmployeeBankDetailsRequest request, CancellationToken ct)
        => Ok(await _employees.ReplaceBankDetailsAsync(employeeId, request, ct));

    [HttpGet("{employeeId:int}/leave-balances")]
    [ProducesResponseType(typeof(List<LeaveBalanceDto>), StatusCodes.Status200OK)]
    public async Task<ActionResult<List<LeaveBalanceDto>>> ListLeaveBalances(int employeeId, [FromQuery] int? year, CancellationToken ct)
        => Ok(await _leaveBalances.ListAsync(employeeId, year, ct));

    [HttpPut("{employeeId:int}/leave-balances")]
    [ProducesResponseType(typeof(List<LeaveBalanceDto>), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<ActionResult<List<LeaveBalanceDto>>> UpdateLeaveBalances(
        int employeeId, [FromBody] UpdateLeaveBalancesRequest request, CancellationToken ct)
        => Ok(await _leaveBalances.ApplyAdjustmentsAsync(employeeId, request, ct));

    [HttpGet("{employeeId:int}/salary-structure")]
    [ProducesResponseType(typeof(List<EmployeeSalaryStructureDto>), StatusCodes.Status200OK)]
    public async Task<ActionResult<List<EmployeeSalaryStructureDto>>> GetSalaryStructureHistory(int employeeId, CancellationToken ct)
        => Ok(await _salaryStructures.ListHistoryAsync(employeeId, ct));

    [HttpPost("{employeeId:int}/salary-structure")]
    [ProducesResponseType(typeof(EmployeeSalaryStructureDto), StatusCodes.Status201Created)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    [ProducesResponseType(StatusCodes.Status409Conflict)]
    public async Task<ActionResult<EmployeeSalaryStructureDto>> CreateSalaryStructure(
        int employeeId, [FromBody] CreateSalaryStructureRequest request, CancellationToken ct)
    {
        var result = await _salaryStructures.CreateAsync(employeeId, request, _currentUser.AuditUserId, ct);
        return Created($"/api/v1/employees/{employeeId}/salary-structure", result);
    }

    [HttpGet("{employeeId:int}/loans")]
    [ProducesResponseType(typeof(List<EmployeeLoanDto>), StatusCodes.Status200OK)]
    public async Task<ActionResult<List<EmployeeLoanDto>>> ListLoans(int employeeId, CancellationToken ct)
        => Ok(await _loans.ListAsync(employeeId, ct));

    [HttpGet("{employeeId:int}/loans/{loanId:int}")]
    [ProducesResponseType(typeof(EmployeeLoanDto), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<ActionResult<EmployeeLoanDto>> GetLoan(int employeeId, int loanId, CancellationToken ct)
    {
        var result = await _loans.GetAsync(employeeId, loanId, ct);
        return result is null ? NotFound() : Ok(result);
    }

    [HttpPost("{employeeId:int}/loans")]
    [ProducesResponseType(typeof(EmployeeLoanDto), StatusCodes.Status201Created)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<ActionResult<EmployeeLoanDto>> CreateLoan(int employeeId, [FromBody] CreateEmployeeLoanRequest request, CancellationToken ct)
    {
        var result = await _loans.CreateAsync(employeeId, request, _currentUser.AuditUserId, ct);
        return Created($"/api/v1/employees/{employeeId}/loans/{result.EmployeeLoanId}", result);
    }

    [HttpGet("{employeeId:int}/documents")]
    [ProducesResponseType(typeof(List<EmployeeDocumentDto>), StatusCodes.Status200OK)]
    public async Task<ActionResult<List<EmployeeDocumentDto>>> ListDocuments(int employeeId, CancellationToken ct)
        => Ok(await _documents.ListAsync(employeeId, ct));

    [HttpPost("{employeeId:int}/documents")]
    [RequestSizeLimit(20 * 1024 * 1024)]
    [ProducesResponseType(typeof(EmployeeDocumentDto), StatusCodes.Status201Created)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    [ProducesResponseType(StatusCodes.Status413PayloadTooLarge)]
    [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
    public async Task<ActionResult<EmployeeDocumentDto>> UploadDocument(
        int employeeId, [FromForm] UploadEmployeeDocumentRequest request, IFormFile file, CancellationToken ct)
    {
        await using var stream = file.OpenReadStream();
        var result = await _documents.UploadAsync(
            employeeId, request, stream, file.FileName, file.ContentType, _currentUser.AuditUserId, ct);
        return Created($"/api/v1/employees/{employeeId}/documents/{result.EmployeeDocumentId}", result);
    }

    [HttpGet("{employeeId:int}/documents/{documentId:int}/download")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> DownloadDocument(int employeeId, int documentId, CancellationToken ct)
    {
        var (content, fileName, contentType) = await _documents.DownloadAsync(employeeId, documentId, ct);
        return File(content, contentType, fileName);
    }

    [HttpPatch("{employeeId:int}/documents/{documentId:int}/status")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> SetDocumentStatus(
        int employeeId, int documentId, [FromBody] UpdateEmployeeDocumentStatusRequest request, CancellationToken ct)
    {
        await _documents.SetStatusAsync(employeeId, documentId, request.Status, ct);
        return NoContent();
    }
}
