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

namespace OmsorgCore.Application.Services;

public class TimeEntryService : ITimeEntryService
{
    private const string StatusListKey = "TimeEntryStatus";

    private readonly ITimeEntryRepository _timeEntryRepository;
    private readonly IOrderRepository _orderRepository;
    private readonly IPermissionService _permissionService;
    private readonly ICurrentUserService _currentUserService;
    private readonly IValueListRepository _valueListRepository;

    public TimeEntryService(
        ITimeEntryRepository timeEntryRepository,
        IOrderRepository orderRepository,
        IPermissionService permissionService,
        ICurrentUserService currentUserService,
        IValueListRepository valueListRepository)
    {
        _timeEntryRepository = timeEntryRepository;
        _orderRepository = orderRepository;
        _permissionService = permissionService;
        _currentUserService = currentUserService;
        _valueListRepository = valueListRepository;
    }

    public async Task<(IReadOnlyList<TimeEntry> Items, int TotalCount)> GetPagedAsync(
        Guid? statusId,
        Guid? employeeId,
        Guid? orderId,
        int page,
        int pageSize,
        CancellationToken cancellationToken = default)
    {
        Guid? restrictToEmployeeId = await ResolveOwnScopeRestrictionAsync(PermissionAction.View, cancellationToken);
        return await _timeEntryRepository.GetPagedAsync(statusId, employeeId, orderId, page, pageSize, cancellationToken, restrictToEmployeeId);
    }

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

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

        return timeEntry;
    }

    public async Task<TimeEntry?> CreateAsync(TimeEntry timeEntry, CancellationToken cancellationToken = default)
    {
        if (await _orderRepository.GetByIdAsync(timeEntry.OrderId, cancellationToken) is null)
        {
            return null;
        }

        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 (analog AbsenceService).
                return null;
            }

            timeEntry.EmployeeId = restrictToEmployeeId.Value;
        }
        else
        {
            var ownEmployeeId = _currentUserService.EmployeeId;
            if (ownEmployeeId is null || ownEmployeeId == Guid.Empty)
            {
                return null;
            }

            timeEntry.EmployeeId = ownEmployeeId.Value;
        }

        var initialStatus = await _valueListRepository.GetInitialItemAsync(StatusListKey, cancellationToken)
            ?? throw new InvalidOperationException("Kein initialer Zeiterfassungsstatus konfiguriert (DbSeeder.SeedValueListsAsync fehlt).");
        timeEntry.StatusId = initialStatus.Id;

        await _timeEntryRepository.AddAsync(timeEntry, cancellationToken);
        await _timeEntryRepository.SaveChangesAsync(cancellationToken);
        return timeEntry;
    }

    public async Task<UpdateTimeEntryResult> UpdateAsync(Guid id, TimeEntry updates, CancellationToken cancellationToken = default)
    {
        var timeEntry = await _timeEntryRepository.GetByIdAsync(id, cancellationToken);
        if (timeEntry is null)
        {
            return UpdateTimeEntryResult.Fail(UpdateTimeEntryFailureReason.NotFound);
        }

        Guid? restrictToEmployeeId = await ResolveOwnScopeRestrictionAsync(PermissionAction.Edit, cancellationToken);
        if (restrictToEmployeeId.HasValue && timeEntry.EmployeeId != restrictToEmployeeId.Value)
        {
            return UpdateTimeEntryResult.Fail(UpdateTimeEntryFailureReason.NotFound);
        }

        var currentStatus = await _valueListRepository.GetItemByIdAsync(timeEntry.StatusId, cancellationToken);
        if (currentStatus is null || !currentStatus.IsEditableByOwner)
        {
            return UpdateTimeEntryResult.Fail(UpdateTimeEntryFailureReason.NotEditable);
        }

        if (updates.OrderId != timeEntry.OrderId && await _orderRepository.GetByIdAsync(updates.OrderId, cancellationToken) is null)
        {
            return UpdateTimeEntryResult.Fail(UpdateTimeEntryFailureReason.OrderNotFound);
        }

        timeEntry.OrderId = updates.OrderId;
        timeEntry.Date = updates.Date;
        timeEntry.Start = updates.Start;
        timeEntry.End = updates.End;
        timeEntry.BreakDuration = updates.BreakDuration;
        timeEntry.NightHours = updates.NightHours;
        timeEntry.SaturdayHours = updates.SaturdayHours;
        timeEntry.SundayHours = updates.SundayHours;
        timeEntry.HolidayHours = updates.HolidayHours;
        timeEntry.UpdatedAt = DateTime.UtcNow;

        await _timeEntryRepository.UpdateAsync(timeEntry, cancellationToken);
        await _timeEntryRepository.SaveChangesAsync(cancellationToken);
        return UpdateTimeEntryResult.Ok(timeEntry);
    }

    public async Task<SubmitTimeEntryResult> SubmitAsync(Guid id, CancellationToken cancellationToken = default)
    {
        var timeEntry = await _timeEntryRepository.GetByIdAsync(id, cancellationToken);
        if (timeEntry is null)
        {
            return SubmitTimeEntryResult.Fail(SubmitTimeEntryFailureReason.NotFound);
        }

        Guid? restrictToEmployeeId = await ResolveOwnScopeRestrictionAsync(PermissionAction.Edit, cancellationToken);
        if (restrictToEmployeeId.HasValue && timeEntry.EmployeeId != restrictToEmployeeId.Value)
        {
            return SubmitTimeEntryResult.Fail(SubmitTimeEntryFailureReason.NotFound);
        }

        var transition = await _valueListRepository.GetSelfServiceTransitionAsync(timeEntry.StatusId, cancellationToken);
        if (transition is null)
        {
            return SubmitTimeEntryResult.Fail(SubmitTimeEntryFailureReason.NoSelfServiceTransition);
        }

        timeEntry.StatusId = transition.ToItemId;
        timeEntry.UpdatedAt = DateTime.UtcNow;

        await _timeEntryRepository.UpdateAsync(timeEntry, cancellationToken);
        await _timeEntryRepository.SaveChangesAsync(cancellationToken);
        return SubmitTimeEntryResult.Ok(timeEntry);
    }

    public async Task<DecideTimeEntryResult> DecideAsync(Guid id, Guid statusId, string? adminNote, CancellationToken cancellationToken = default)
    {
        var timeEntry = await _timeEntryRepository.GetByIdAsync(id, cancellationToken);
        if (timeEntry is null)
        {
            return DecideTimeEntryResult.Fail(DecideTimeEntryFailureReason.NotFound);
        }

        var transition = await _valueListRepository.GetTransitionAsync(timeEntry.StatusId, statusId, cancellationToken);
        if (transition is null || !transition.RequiresApproval)
        {
            return DecideTimeEntryResult.Fail(DecideTimeEntryFailureReason.InvalidStatusTransition);
        }

        timeEntry.StatusId = statusId;
        timeEntry.AdminNote = adminNote;
        timeEntry.UpdatedAt = DateTime.UtcNow;

        await _timeEntryRepository.UpdateAsync(timeEntry, cancellationToken);
        await _timeEntryRepository.SaveChangesAsync(cancellationToken);
        return DecideTimeEntryResult.Ok(timeEntry);
    }

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

        return deleted;
    }

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

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

        return restored;
    }

    /// <summary>Own-Scope-Filterung, siehe AbsenceService.ResolveOwnScopeRestrictionAsync (analoges Muster für ModuleType.TimeEntries).</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.TimeEntries, action, cancellationToken);
        return scope == PermissionScope.Own ? (_currentUserService.EmployeeId ?? Guid.Empty) : null;
    }
}
