using Microsoft.Extensions.Caching.Memory;
using OmsorgCore.Application.Services;
using OmsorgCore.Domain.Entities;
using OmsorgCore.Domain.Enums;
using OmsorgCore.Tests.TestDoubles;

namespace OmsorgCore.Tests.Services;

public class ContractServiceTests
{
    private static PermissionService CreatePermissionService()
        => new(new FakeUserRepository(), new MemoryCache(new MemoryCacheOptions()));

    [Fact]
    public async Task CreateAsync_PersistsAllFields()
    {
        var repository = new FakeContractRepository();
        var sut = new ContractService(repository, CreatePermissionService(), new FakeCurrentUserService());
        var employeeId = Guid.NewGuid();

        var contract = new Contract
        {
            ContractType = "Arbeitsvertrag",
            EmployeeId = employeeId,
            StartDate = new DateOnly(2026, 1, 1),
            EndDate = new DateOnly(2026, 12, 31),
            WeeklyHours = 38.5m,
            HourlyWage = 21.75m,
            AllowancesDescription = "Nachtzuschlag 25%",
            OvertimeRules = "Abgeltung über Freizeitausgleich",
            VacationDaysPerYear = 28,
            ProbationPeriodMonths = 6
        };

        var created = await sut.CreateAsync(contract);
        var stored = await sut.GetByIdAsync(created.Id);

        Assert.NotNull(stored);
        Assert.Equal("Arbeitsvertrag", stored!.ContractType);
        Assert.Equal(employeeId, stored.EmployeeId);
        Assert.Equal(new DateOnly(2026, 1, 1), stored.StartDate);
        Assert.Equal(new DateOnly(2026, 12, 31), stored.EndDate);
        Assert.Equal(38.5m, stored.WeeklyHours);
        Assert.Equal(21.75m, stored.HourlyWage);
        Assert.Equal("Nachtzuschlag 25%", stored.AllowancesDescription);
        Assert.Equal("Abgeltung über Freizeitausgleich", stored.OvertimeRules);
        Assert.Equal(28, stored.VacationDaysPerYear);
        Assert.Equal(6, stored.ProbationPeriodMonths);
        Assert.Equal("Entwurf", stored.Status);
    }

    [Fact]
    public async Task GetAllAsync_ReturnsCreatedContracts()
    {
        var repository = new FakeContractRepository();
        var sut = new ContractService(repository, CreatePermissionService(), new FakeCurrentUserService());

        await sut.CreateAsync(new Contract { ContractType = "Rahmenvertrag", FacilityId = Guid.NewGuid(), StartDate = new DateOnly(2026, 1, 1) });

        var all = await sut.GetAllAsync();

        Assert.Single(all);
    }

    [Fact]
    public async Task UpdateAsync_UpdatesAllMutableFields_AndReturnsUpdatedContract()
    {
        var repository = new FakeContractRepository();
        var sut = new ContractService(repository, CreatePermissionService(), new FakeCurrentUserService());
        var employeeId = Guid.NewGuid();
        var created = await sut.CreateAsync(new Contract
        {
            ContractType = "Arbeitsvertrag",
            EmployeeId = employeeId,
            StartDate = new DateOnly(2026, 1, 1)
        });

        var updates = new Contract
        {
            ContractType = "Arbeitsvertrag",
            EmployeeId = employeeId,
            StartDate = new DateOnly(2026, 1, 1),
            EndDate = new DateOnly(2026, 6, 30),
            Status = "Aktiv",
            WeeklyHours = 40m,
            HourlyWage = 22.5m,
            VacationDaysPerYear = 30,
            ProbationPeriodMonths = 3
        };

        var updated = await sut.UpdateAsync(created.Id, updates);

        Assert.NotNull(updated);
        Assert.Equal("Aktiv", updated!.Status);
        Assert.Equal(new DateOnly(2026, 6, 30), updated.EndDate);
        Assert.Equal(40m, updated.WeeklyHours);
        Assert.Equal(22.5m, updated.HourlyWage);
        Assert.Equal(30, updated.VacationDaysPerYear);
        Assert.Equal(3, updated.ProbationPeriodMonths);
        Assert.NotNull(updated.UpdatedAt);
    }

    [Fact]
    public async Task UpdateAsync_UnknownId_ReturnsNull()
    {
        var repository = new FakeContractRepository();
        var sut = new ContractService(repository, CreatePermissionService(), new FakeCurrentUserService());

        var updated = await sut.UpdateAsync(Guid.NewGuid(), new Contract { ContractType = "X", StartDate = new DateOnly(2026, 1, 1) });

        Assert.Null(updated);
    }

    [Fact]
    public async Task GetPagedAsync_FiltersByStatusEmployeeAndFacility_AndPaginates()
    {
        var repository = new FakeContractRepository();
        var sut = new ContractService(repository, CreatePermissionService(), new FakeCurrentUserService());
        var employeeA = Guid.NewGuid();
        var employeeB = Guid.NewGuid();
        var facility = Guid.NewGuid();

        await sut.CreateAsync(new Contract { ContractType = "Arbeitsvertrag", EmployeeId = employeeA, StartDate = new DateOnly(2026, 1, 1), Status = "Aktiv" });
        await sut.CreateAsync(new Contract { ContractType = "Arbeitsvertrag", EmployeeId = employeeB, StartDate = new DateOnly(2026, 2, 1), Status = "Aktiv" });
        await sut.CreateAsync(new Contract { ContractType = "Rahmenvertrag", FacilityId = facility, StartDate = new DateOnly(2026, 3, 1), Status = "Entwurf" });

        var (items, totalCount) = await sut.GetPagedAsync(search: null, status: "Aktiv", employeeId: employeeA, facilityId: null, page: 1, pageSize: 10);

        Assert.Equal(1, totalCount);
        Assert.Single(items);
        Assert.Equal(employeeA, items[0].EmployeeId);
    }

    [Fact]
    public async Task GetPagedAsync_PaginatesResults()
    {
        var repository = new FakeContractRepository();
        var sut = new ContractService(repository, CreatePermissionService(), new FakeCurrentUserService());

        await sut.CreateAsync(new Contract { ContractType = "A", StartDate = new DateOnly(2026, 1, 1), EmployeeId = Guid.NewGuid() });
        await sut.CreateAsync(new Contract { ContractType = "B", StartDate = new DateOnly(2026, 2, 1), EmployeeId = Guid.NewGuid() });
        await sut.CreateAsync(new Contract { ContractType = "C", StartDate = new DateOnly(2026, 3, 1), EmployeeId = Guid.NewGuid() });

        var (items, totalCount) = await sut.GetPagedAsync(search: null, status: null, employeeId: null, facilityId: null, page: 2, pageSize: 2);

        Assert.Equal(3, totalCount);
        Assert.Single(items);
    }

    private static (User User, FakeUserRepository Users) CreateOwnScopedAussendienstUser(Guid? linkedEmployeeId)
    {
        var role = new Role { Name = "Außendienst" };
        role.RolePermissions.Add(new RolePermission { Role = role, RoleId = role.Id, Module = ModuleType.Contracts, Action = PermissionAction.View, Scope = PermissionScope.Own });
        var user = new User
        {
            Username = "aussendienst",
            PasswordHash = "hashed:secret",
            IsActive = true,
            RoleId = role.Id,
            Role = role,
            EmployeeId = linkedEmployeeId
        };
        return (user, new FakeUserRepository(user));
    }

    [Fact]
    public async Task GetPagedAsync_OwnScope_ReturnsOnlyOwnContractsRegardlessOfRequestedEmployeeId()
    {
        var repository = new FakeContractRepository();
        var ownEmployeeId = Guid.NewGuid();
        var foreignEmployeeId = Guid.NewGuid();
        var (user, users) = CreateOwnScopedAussendienstUser(ownEmployeeId);
        var permissionService = new PermissionService(users, new MemoryCache(new MemoryCacheOptions()));
        var currentUser = new FakeCurrentUserService { UserId = user.Id, EmployeeId = ownEmployeeId };
        var sut = new ContractService(repository, permissionService, currentUser);

        await sut.CreateAsync(new Contract { ContractType = "Arbeitsvertrag", EmployeeId = ownEmployeeId, StartDate = new DateOnly(2026, 1, 1) });
        await sut.CreateAsync(new Contract { ContractType = "Arbeitsvertrag", EmployeeId = foreignEmployeeId, StartDate = new DateOnly(2026, 1, 1) });

        // Own-User fragt versuchsweise fremde EmployeeId ab - Backend muss trotzdem auf die eigene erzwingen.
        var (items, totalCount) = await sut.GetPagedAsync(search: null, status: null, employeeId: foreignEmployeeId, facilityId: null, page: 1, pageSize: 10);

        Assert.Equal(1, totalCount);
        Assert.Equal(ownEmployeeId, Assert.Single(items).EmployeeId);
    }

    [Fact]
    public async Task GetByIdAsync_OwnScope_ForeignContract_ReturnsNull()
    {
        var repository = new FakeContractRepository();
        var ownEmployeeId = Guid.NewGuid();
        var (user, users) = CreateOwnScopedAussendienstUser(ownEmployeeId);
        var permissionService = new PermissionService(users, new MemoryCache(new MemoryCacheOptions()));
        var currentUser = new FakeCurrentUserService { UserId = user.Id, EmployeeId = ownEmployeeId };
        var sut = new ContractService(repository, permissionService, currentUser);

        var own = await sut.CreateAsync(new Contract { ContractType = "Arbeitsvertrag", EmployeeId = ownEmployeeId, StartDate = new DateOnly(2026, 1, 1) });
        var foreign = await sut.CreateAsync(new Contract { ContractType = "Arbeitsvertrag", EmployeeId = Guid.NewGuid(), StartDate = new DateOnly(2026, 1, 1) });

        Assert.NotNull(await sut.GetByIdAsync(own.Id));
        Assert.Null(await sut.GetByIdAsync(foreign.Id));
    }
}
