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

namespace OmsorgCore.Api.Controllers;

/// <summary>
/// Strukturierte Zeiterfassung pro Schicht (FR-ZE-1) mit Statuspipeline
/// Entwurf -&gt; Eingereicht -&gt; Prüfung -&gt; Rückfrage -&gt; Freigegeben -&gt; Abgerechnet (FR-ZE-2). Außendienst
/// darf nur Create/View/Edit mit PermissionScope.Own (eigene Einträge, EmployeeId wird serverseitig
/// aus dem JWT gesetzt, siehe TimeEntryService.CreateAsync) und die Selbst-Einreichungs-Kante über
/// <see cref="Submit"/> auslösen; Büro-Rollen entscheiden über <see cref="Decide"/> (Approve).
/// </summary>
[ApiController]
[Authorize]
[Route("api/time-entries")]
public class TimeEntriesController : ControllerBase
{
    private readonly ITimeEntryService _timeEntryService;

    public TimeEntriesController(ITimeEntryService timeEntryService)
    {
        _timeEntryService = timeEntryService;
    }

    [HttpGet]
    [RequirePermission(ModuleType.TimeEntries, PermissionAction.View)]
    public async Task<ActionResult<PagedResponse<TimeEntryResponse>>> GetAll(
        [FromQuery] Guid? statusId,
        [FromQuery] Guid? employeeId,
        [FromQuery] Guid? orderId,
        [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 _timeEntryService.GetPagedAsync(statusId, employeeId, orderId, page, pageSize, cancellationToken);
        return Ok(new PagedResponse<TimeEntryResponse>(items.Select(ToResponse).ToList(), totalCount, page, pageSize));
    }

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

    [HttpPost]
    [RequirePermission(ModuleType.TimeEntries, PermissionAction.Create)]
    public async Task<ActionResult<TimeEntryResponse>> Create(CreateTimeEntryRequest request, CancellationToken cancellationToken)
    {
        var fieldError = ValidateFields(request.Start, request.End, request.NightHours, request.SaturdayHours, request.SundayHours, request.HolidayHours);
        if (fieldError is not null)
        {
            return BadRequest(fieldError);
        }

        var timeEntry = new TimeEntry
        {
            OrderId = request.OrderId,
            Date = request.Date,
            Start = request.Start,
            End = request.End,
            BreakDuration = request.BreakDuration,
            NightHours = request.NightHours,
            SaturdayHours = request.SaturdayHours,
            SundayHours = request.SundayHours,
            HolidayHours = request.HolidayHours
        };

        var created = await _timeEntryService.CreateAsync(timeEntry, cancellationToken);
        if (created is null)
        {
            return BadRequest("Zeiterfassung konnte nicht angelegt werden - entweder kein verknüpfter Mitarbeiter oder der Auftrag existiert nicht.");
        }

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

    [HttpPut("{id:guid}")]
    [RequirePermission(ModuleType.TimeEntries, PermissionAction.Edit)]
    public async Task<ActionResult<TimeEntryResponse>> Update(Guid id, UpdateTimeEntryRequest request, CancellationToken cancellationToken)
    {
        var fieldError = ValidateFields(request.Start, request.End, request.NightHours, request.SaturdayHours, request.SundayHours, request.HolidayHours);
        if (fieldError is not null)
        {
            return BadRequest(fieldError);
        }

        var updates = new TimeEntry
        {
            OrderId = request.OrderId,
            Date = request.Date,
            Start = request.Start,
            End = request.End,
            BreakDuration = request.BreakDuration,
            NightHours = request.NightHours,
            SaturdayHours = request.SaturdayHours,
            SundayHours = request.SundayHours,
            HolidayHours = request.HolidayHours
        };

        var result = await _timeEntryService.UpdateAsync(id, updates, cancellationToken);
        if (!result.Success)
        {
            return result.FailureReason switch
            {
                UpdateTimeEntryFailureReason.NotEditable => BadRequest("Die Zeiterfassung wurde bereits zur Prüfung übergeben und kann nicht mehr bearbeitet werden."),
                UpdateTimeEntryFailureReason.OrderNotFound => BadRequest("Der angegebene Auftrag existiert nicht."),
                _ => NotFound()
            };
        }

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

    [HttpPost("{id:guid}/submit")]
    [RequirePermission(ModuleType.TimeEntries, PermissionAction.Edit)]
    public async Task<ActionResult<TimeEntryResponse>> Submit(Guid id, CancellationToken cancellationToken)
    {
        var result = await _timeEntryService.SubmitAsync(id, cancellationToken);
        if (!result.Success)
        {
            return result.FailureReason == SubmitTimeEntryFailureReason.NoSelfServiceTransition
                ? BadRequest("Aus dem aktuellen Status ist keine Einreichung möglich.")
                : NotFound();
        }

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

    [HttpPost("{id:guid}/decision")]
    [RequirePermission(ModuleType.TimeEntries, PermissionAction.Approve)]
    public async Task<ActionResult<TimeEntryResponse>> Decide(Guid id, TimeEntryDecisionRequest request, CancellationToken cancellationToken)
    {
        if (request.AdminNote is { Length: > 500 })
        {
            return BadRequest("AdminNote darf maximal 500 Zeichen lang sein.");
        }

        var result = await _timeEntryService.DecideAsync(id, request.StatusId, request.AdminNote, cancellationToken);
        if (!result.Success)
        {
            return result.FailureReason == DecideTimeEntryFailureReason.InvalidStatusTransition
                ? BadRequest("Der Statuswechsel ist nicht zulässig.")
                : NotFound();
        }

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

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

    // End < Start ist bewusst erlaubt (Nachtschichten, die über Mitternacht gehen) - keine
    // Start/Ende-Reihenfolge-Prüfung wie bei Absence.StartDate/EndDate.
    private static string? ValidateFields(TimeOnly start, TimeOnly end, decimal nightHours, decimal saturdayHours, decimal sundayHours, decimal holidayHours)
    {
        if (nightHours < 0 || saturdayHours < 0 || sundayHours < 0 || holidayHours < 0)
        {
            return "Zuschlagsstunden dürfen nicht negativ sein.";
        }

        return null;
    }

    private static TimeEntryResponse ToResponse(TimeEntry timeEntry)
        => new(
            timeEntry.Id,
            timeEntry.EmployeeId,
            timeEntry.Employee is null ? string.Empty : $"{timeEntry.Employee.FirstName} {timeEntry.Employee.LastName}",
            timeEntry.OrderId,
            timeEntry.Order?.FacilityId ?? Guid.Empty,
            timeEntry.Order?.Facility?.Name ?? string.Empty,
            timeEntry.Date,
            timeEntry.Start,
            timeEntry.End,
            timeEntry.BreakDuration,
            timeEntry.NightHours,
            timeEntry.SaturdayHours,
            timeEntry.SundayHours,
            timeEntry.HolidayHours,
            timeEntry.StatusId,
            timeEntry.Status?.Value ?? string.Empty,
            timeEntry.Status?.IsEditableByOwner ?? false,
            timeEntry.AdminNote,
            timeEntry.CreatedAt);
}
