using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using OmsorgCore.Api.Contracts;
using OmsorgCore.Api.Security;
using OmsorgCore.Application.Abstractions;
using OmsorgCore.Application.Services;
using OmsorgCore.Domain.Entities;
using OmsorgCore.Domain.Enums;

namespace OmsorgCore.Api.Controllers;

/// <summary>
/// Abwesenheits-/Urlaubs-/Krankmeldungsanträge (FR-CON-1, Datenbasis für FR-EM-3). Außendienst
/// darf nur Create/View mit PermissionScope.Own (eigene Anträge, EmployeeId wird serverseitig aus
/// dem JWT gesetzt, siehe AbsenceService.CreateAsync), Büro-Rollen sehen/entscheiden über alle.
/// </summary>
[ApiController]
[Authorize]
[Route("api/absences")]
public class AbsencesController : ControllerBase
{
    private const string AbsenceTypeListKey = "AbsenceType";
    private const string AbsenceStatusListKey = "AbsenceStatus";

    private readonly IAbsenceService _absenceService;
    private readonly IValueListRepository _valueListRepository;

    public AbsencesController(IAbsenceService absenceService, IValueListRepository valueListRepository)
    {
        _absenceService = absenceService;
        _valueListRepository = valueListRepository;
    }

    [HttpGet]
    [RequirePermission(ModuleType.Absences, PermissionAction.View)]
    public async Task<ActionResult<PagedResponse<AbsenceResponse>>> GetAll(
        [FromQuery] string? status,
        [FromQuery] string? type,
        [FromQuery] Guid? employeeId,
        [FromQuery] int page = 1,
        [FromQuery] int pageSize = 20,
        CancellationToken cancellationToken = default)
    {
        page = Math.Max(page, 1);
        pageSize = Math.Clamp(pageSize, 1, 100);

        var (items, totalCount) = await _absenceService.GetPagedAsync(status, type, employeeId, page, pageSize, cancellationToken);
        return Ok(new PagedResponse<AbsenceResponse>(items.Select(ToResponse).ToList(), totalCount, page, pageSize));
    }

    [HttpGet("{id:guid}")]
    [RequirePermission(ModuleType.Absences, PermissionAction.View)]
    public async Task<ActionResult<AbsenceResponse>> GetById(Guid id, CancellationToken cancellationToken)
    {
        var absence = await _absenceService.GetByIdAsync(id, cancellationToken);
        return absence is null ? NotFound() : Ok(ToResponse(absence));
    }

    [HttpPost]
    [RequirePermission(ModuleType.Absences, PermissionAction.Create)]
    public async Task<ActionResult<AbsenceResponse>> Create(CreateAbsenceRequest request, CancellationToken cancellationToken)
    {
        var fieldError = await ValidateFieldsAsync(request.Type, request.StartDate, request.EndDate, request.Reason, request.Substitute, request.Note, cancellationToken);
        if (fieldError is not null)
        {
            return BadRequest(fieldError);
        }

        var absence = new Absence
        {
            Type = request.Type,
            StartDate = request.StartDate,
            EndDate = request.EndDate,
            Reason = request.Reason,
            Substitute = request.Substitute,
            Note = request.Note
        };

        var created = await _absenceService.CreateAsync(absence, cancellationToken);
        if (created is null)
        {
            return BadRequest("Kein Mitarbeiter verknüpft - Abwesenheitsanträge können nur für einen verknüpften Mitarbeiter angelegt werden.");
        }

        var reloaded = await _absenceService.GetByIdAsync(created.Id, cancellationToken);
        return CreatedAtAction(nameof(GetById), new { id = created.Id }, ToResponse(reloaded!));
    }

    [HttpPut("{id:guid}")]
    [RequirePermission(ModuleType.Absences, PermissionAction.Edit)]
    public async Task<ActionResult<AbsenceResponse>> Update(Guid id, UpdateAbsenceRequest request, CancellationToken cancellationToken)
    {
        var fieldError = await ValidateFieldsAsync(request.Type, request.StartDate, request.EndDate, request.Reason, request.Substitute, request.Note, cancellationToken);
        if (fieldError is not null)
        {
            return BadRequest(fieldError);
        }

        var updates = new Absence
        {
            Type = request.Type,
            StartDate = request.StartDate,
            EndDate = request.EndDate,
            Reason = request.Reason,
            Substitute = request.Substitute,
            Note = request.Note
        };

        var result = await _absenceService.UpdateAsync(id, updates, cancellationToken);
        if (!result.Success)
        {
            return result.FailureReason == UpdateAbsenceFailureReason.AlreadyDecided
                ? BadRequest("Der Antrag wurde bereits entschieden und kann nicht mehr bearbeitet werden.")
                : NotFound();
        }

        var reloaded = await _absenceService.GetByIdAsync(result.Absence!.Id, cancellationToken);
        return Ok(ToResponse(reloaded!));
    }

    [HttpPost("{id:guid}/decision")]
    [RequirePermission(ModuleType.Absences, PermissionAction.Approve)]
    public async Task<ActionResult<AbsenceResponse>> Decide(Guid id, AbsenceDecisionRequest request, CancellationToken cancellationToken)
    {
        var allowedStatuses = await _valueListRepository.GetActiveValuesAsync(AbsenceStatusListKey, cancellationToken);
        var initialStatus = await _absenceService.GetInitialStatusValueAsync(cancellationToken);
        var decidableStatuses = allowedStatuses.Where(s => s != initialStatus).ToList();
        if (string.IsNullOrWhiteSpace(request.Status) || !decidableStatuses.Contains(request.Status))
        {
            return BadRequest($"Status muss einer der folgenden Werte sein: {string.Join(", ", decidableStatuses)}.");
        }

        if (request.AdminNote is { Length: > 500 })
        {
            return BadRequest("AdminNote darf maximal 500 Zeichen lang sein.");
        }

        var decided = await _absenceService.DecideAsync(id, request.Status, request.AdminNote, cancellationToken);
        return decided is null ? NotFound() : Ok(ToResponse(decided));
    }

    [HttpDelete("{id:guid}")]
    [RequirePermission(ModuleType.Absences, PermissionAction.Delete)]
    public async Task<IActionResult> Delete(Guid id, CancellationToken cancellationToken)
    {
        var deleted = await _absenceService.DeleteAsync(id, cancellationToken);
        return deleted ? NoContent() : NotFound();
    }

    private async Task<string?> ValidateFieldsAsync(
        string type,
        DateOnly startDate,
        DateOnly endDate,
        string? reason,
        string? substitute,
        string? note,
        CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(type))
        {
            return "Type ist erforderlich.";
        }

        var allowedTypes = await _valueListRepository.GetActiveValuesAsync(AbsenceTypeListKey, cancellationToken);
        if (!allowedTypes.Contains(type))
        {
            return $"Type muss einer der folgenden Werte sein: {string.Join(", ", allowedTypes)}.";
        }

        if (startDate == default)
        {
            return "StartDate ist erforderlich.";
        }

        if (endDate < startDate)
        {
            return "EndDate darf nicht vor StartDate liegen.";
        }

        if (reason is { Length: > 500 })
        {
            return "Reason darf maximal 500 Zeichen lang sein.";
        }

        if (substitute is { Length: > 200 })
        {
            return "Substitute darf maximal 200 Zeichen lang sein.";
        }

        if (note is { Length: > 500 })
        {
            return "Note darf maximal 500 Zeichen lang sein.";
        }

        return null;
    }

    private static AbsenceResponse ToResponse(Absence absence)
        => new(
            absence.Id,
            absence.EmployeeId,
            absence.Employee is null ? string.Empty : $"{absence.Employee.FirstName} {absence.Employee.LastName}",
            absence.Type,
            absence.StartDate,
            absence.EndDate,
            absence.Reason,
            absence.Substitute,
            absence.Note,
            absence.Status,
            absence.AdminNote,
            absence.CreatedAt);
}
