using OmsorgCore.Application.Abstractions;
using OmsorgCore.Domain.Entities;
using OmsorgCore.Domain.Enums;

namespace OmsorgCore.Application.Services;

public class AbsenceService : IAbsenceService
{
    private const string StatusListKey = "AbsenceStatus";

    private readonly IAbsenceRepository _absenceRepository;
    private readonly IPermissionService _permissionService;
    private readonly ICurrentUserService _currentUserService;
    private readonly IValueListRepository _valueListRepository;

    public AbsenceService(
        IAbsenceRepository absenceRepository,
        IPermissionService permissionService,
        ICurrentUserService currentUserService,
        IValueListRepository valueListRepository)
    {
        _absenceRepository = absenceRepository;
        _permissionService = permissionService;
        _currentUserService = currentUserService;
        _valueListRepository = valueListRepository;
    }

    public async Task<(IReadOnlyList<Absence> Items, int TotalCount)> GetPagedAsync(
        string? status,
        string? type,
        Guid? employeeId,
        int page,
        int pageSize,
        CancellationToken cancellationToken = default)
    {
        Guid? restrictToEmployeeId = await ResolveOwnScopeRestrictionAsync(PermissionAction.View, cancellationToken);
        return await _absenceRepository.GetPagedAsync(status, type, employeeId, page, pageSize, cancellationToken, restrictToEmployeeId);
    }

    public async Task<Absence?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
    {
        var absence = await _absenceRepository.GetByIdAsync(id, cancellationToken);
        if (absence is null)
        {
            return null;
        }

        Guid? restrictToEmployeeId = await ResolveOwnScopeRestrictionAsync(PermissionAction.View, cancellationToken);
        if (restrictToEmployeeId.HasValue && absence.EmployeeId != restrictToEmployeeId.Value)
        {
            return null;
        }

        return absence;
    }

    public async Task<Absence?> CreateAsync(Absence absence, CancellationToken cancellationToken = default)
    {
        Guid? restrictToEmployeeId = await ResolveOwnScopeRestrictionAsync(PermissionAction.Create, cancellationToken);
        if (restrictToEmployeeId.HasValue)
        {
            if (restrictToEmployeeId.Value == Guid.Empty)
            {
                // Own-Scope, aber kein User.EmployeeId verknüpft - fail-closed statt "für niemanden" anzulegen.
                return null;
            }

            absence.EmployeeId = restrictToEmployeeId.Value;
        }
        else
        {
            // All-Scope-Aufrufer (z. B. Büro-Rollen/Administrator): CreateAbsenceRequest hat bewusst
            // kein employeeId-Feld ("im Namen von" ist nicht Teil dieser ersten UI, siehe
            // omsorgCore/CLAUDE.md). Own-Scope ist nur eine Sichtbarkeits-/Anlege-Einschränkung
            // ("nur eigene Daten"), kein Ausschlusskriterium dafür, ob man überhaupt einen eigenen
            // Antrag stellen darf - auch Büro-Rollen sind Mitarbeiter und wollen eigenen Urlaub
            // beantragen können (Vorfall 2026-08-10: Admin mit verknüpftem Mitarbeiter bekam grundlos
            // 400). Deshalb hier Fallback auf die eigene verknüpfte Mitarbeiter-Id; nur wenn die
            // Aufrufer:in selbst gar keinen Mitarbeiter verknüpft hat, bleibt es ein harter Fehler.
            var ownEmployeeId = _currentUserService.EmployeeId;
            if (ownEmployeeId is null || ownEmployeeId == Guid.Empty)
            {
                return null;
            }

            absence.EmployeeId = ownEmployeeId.Value;
        }

        // Nicht den Entity-Default hartkodiert übernehmen - der Wert kommt aus der admin-editierbaren
        // ValueList "AbsenceStatus" (IsInitial-Flag, von DbSeeder.SeedValueListsAsync gesetzt), damit
        // ein Umbenennen über die Status-Verwaltung nicht lautlos bricht (analog OrderService.CreateAsync).
        absence.Status = await GetInitialStatusValueAsync(cancellationToken);

        await _absenceRepository.AddAsync(absence, cancellationToken);
        await _absenceRepository.SaveChangesAsync(cancellationToken);
        return absence;
    }

    public async Task<UpdateAbsenceResult> UpdateAsync(Guid id, Absence updates, CancellationToken cancellationToken = default)
    {
        var absence = await _absenceRepository.GetByIdAsync(id, cancellationToken);
        if (absence is null)
        {
            return UpdateAbsenceResult.Fail(UpdateAbsenceFailureReason.NotFound);
        }

        Guid? restrictToEmployeeId = await ResolveOwnScopeRestrictionAsync(PermissionAction.Edit, cancellationToken);
        if (restrictToEmployeeId.HasValue && absence.EmployeeId != restrictToEmployeeId.Value)
        {
            // Wie GetByIdAsync: fremden Own-Scope-Antrag als "nicht gefunden" behandeln statt 403,
            // um nicht zu verraten, dass die Id überhaupt existiert.
            return UpdateAbsenceResult.Fail(UpdateAbsenceFailureReason.NotFound);
        }

        if (absence.Status != await GetInitialStatusValueAsync(cancellationToken))
        {
            return UpdateAbsenceResult.Fail(UpdateAbsenceFailureReason.AlreadyDecided);
        }

        absence.Type = updates.Type;
        absence.StartDate = updates.StartDate;
        absence.EndDate = updates.EndDate;
        absence.Reason = updates.Reason;
        absence.Substitute = updates.Substitute;
        absence.Note = updates.Note;
        absence.UpdatedAt = DateTime.UtcNow;

        await _absenceRepository.UpdateAsync(absence, cancellationToken);
        await _absenceRepository.SaveChangesAsync(cancellationToken);
        return UpdateAbsenceResult.Ok(absence);
    }

    public async Task<Absence?> DecideAsync(Guid id, string status, string? adminNote, CancellationToken cancellationToken = default)
    {
        var absence = await _absenceRepository.GetByIdAsync(id, cancellationToken);
        if (absence is null)
        {
            return null;
        }

        absence.Status = status;
        absence.AdminNote = adminNote;
        absence.UpdatedAt = DateTime.UtcNow;

        await _absenceRepository.UpdateAsync(absence, cancellationToken);
        await _absenceRepository.SaveChangesAsync(cancellationToken);
        return absence;
    }

    public async Task<bool> DeleteAsync(Guid id, CancellationToken cancellationToken = default)
    {
        var deleted = await _absenceRepository.SoftDeleteAsync(id, cancellationToken);
        if (deleted)
        {
            await _absenceRepository.SaveChangesAsync(cancellationToken);
        }

        return deleted;
    }

    public Task<IReadOnlyList<Absence>> GetDeletedAsync(string? search, CancellationToken cancellationToken = default)
        => _absenceRepository.GetDeletedAsync(search, cancellationToken);

    public async Task<bool> RestoreAsync(Guid id, CancellationToken cancellationToken = default)
    {
        var restored = await _absenceRepository.RestoreAsync(id, cancellationToken);
        if (restored)
        {
            await _absenceRepository.SaveChangesAsync(cancellationToken);
        }

        return restored;
    }

    /// <summary>
    /// Der aktuell als "initial"/pending markierte Status-Wert der ValueList "AbsenceStatus"
    /// (per Default "Eingereicht", aber nicht hartkodiert - über Status-Verwaltung umbenennbar,
    /// ohne dass diese Logik oder der Controller (Decide-Endpoint) mitgeändert werden müssten).
    /// </summary>
    public async Task<string> GetInitialStatusValueAsync(CancellationToken cancellationToken = default)
    {
        var initial = await _valueListRepository.GetInitialItemAsync(StatusListKey, cancellationToken);
        return initial?.Value
            ?? throw new InvalidOperationException("Kein initialer Abwesenheitsstatus konfiguriert (DbSeeder.SeedValueListsAsync fehlt).");
    }

    /// <summary>Own-Scope-Filterung, siehe EmployeeService.ResolveOwnScopeRestrictionAsync (analoges Muster für ModuleType.Absences).</summary>
    private async Task<Guid?> ResolveOwnScopeRestrictionAsync(PermissionAction action, CancellationToken cancellationToken)
    {
        if (_currentUserService.UserId is not { } userId)
        {
            return null;
        }

        var scope = await _permissionService.GetScopeAsync(userId, ModuleType.Absences, action, cancellationToken);
        return scope == PermissionScope.Own ? (_currentUserService.EmployeeId ?? Guid.Empty) : null;
    }
}
