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 EmployeeServiceTests
{
    private static PermissionService CreatePermissionService()
        => new(new FakeUserRepository(), new MemoryCache(new MemoryCacheOptions()));

    [Fact]
    public async Task CreateAsync_PersistsAllStammdatenFields()
    {
        var repository = new FakeEmployeeRepository();
        var sut = new EmployeeService(repository, CreatePermissionService(), new FakeCurrentUserService());

        var employee = new Employee
        {
            FirstName = "Sabrina",
            LastName = "Berggötz",
            DateOfBirth = new DateOnly(1990, 5, 1),
            Street = "Musterstraße 1",
            PostalCode = "12345",
            City = "Musterstadt",
            Country = "Deutschland",
            PhoneNumber = "0170 1234567",
            Email = "sabrina@omsorg.de",
            EntryDate = new DateOnly(2024, 1, 1),
            ExitDate = null,
            EmergencyContactName = "Markus Berggötz",
            EmergencyContactPhone = "0170 7654321",
            EmergencyContactRelation = "Ehepartner",
            EmploymentType = "Vollzeit",
            Qualification = "Examinierte Pflegefachkraft"
        };

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

        Assert.NotNull(stored);
        Assert.Equal("Sabrina", stored!.FirstName);
        Assert.Equal("Berggötz", stored.LastName);
        Assert.Equal(new DateOnly(1990, 5, 1), stored.DateOfBirth);
        Assert.Equal("Musterstraße 1", stored.Street);
        Assert.Equal("12345", stored.PostalCode);
        Assert.Equal("Musterstadt", stored.City);
        Assert.Equal("Deutschland", stored.Country);
        Assert.Equal("0170 1234567", stored.PhoneNumber);
        Assert.Equal("sabrina@omsorg.de", stored.Email);
        Assert.Equal(new DateOnly(2024, 1, 1), stored.EntryDate);
        Assert.Null(stored.ExitDate);
        Assert.Equal("Aktiv", stored.Status);
        Assert.Equal("Markus Berggötz", stored.EmergencyContactName);
        Assert.Equal("0170 7654321", stored.EmergencyContactPhone);
        Assert.Equal("Ehepartner", stored.EmergencyContactRelation);
        Assert.Equal("Vollzeit", stored.EmploymentType);
        Assert.Equal("Examinierte Pflegefachkraft", stored.Qualification);
    }

    [Fact]
    public async Task GetAllAsync_ReturnsCreatedEmployees()
    {
        var repository = new FakeEmployeeRepository();
        var sut = new EmployeeService(repository, CreatePermissionService(), new FakeCurrentUserService());

        await sut.CreateAsync(new Employee { FirstName = "Sascha", LastName = "Recruiting" });

        var all = await sut.GetAllAsync();

        Assert.Single(all);
    }

    [Fact]
    public async Task UpdateAsync_UpdatesAllMutableFields_AndReturnsUpdatedEmployee()
    {
        var repository = new FakeEmployeeRepository();
        var sut = new EmployeeService(repository, CreatePermissionService(), new FakeCurrentUserService());
        var created = await sut.CreateAsync(new Employee { FirstName = "Sabrina", LastName = "Berggötz" });

        var updates = new Employee
        {
            FirstName = "Sabrina",
            LastName = "Kowalski",
            Status = "Inaktiv",
            DateOfBirth = new DateOnly(1990, 5, 1),
            Street = "Neue Straße 2",
            PostalCode = "54321",
            City = "Neustadt",
            Country = "Deutschland",
            PhoneNumber = "0171 1111111",
            Email = "sabrina.kowalski@omsorg.de",
            EntryDate = new DateOnly(2024, 1, 1),
            ExitDate = new DateOnly(2024, 12, 31),
            EmergencyContactName = "Markus Berggötz",
            EmergencyContactPhone = "0170 7654321",
            EmergencyContactRelation = "Ehepartner",
            EmploymentType = "Teilzeit",
            Qualification = "Pflegehelferin"
        };

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

        Assert.NotNull(updated);
        Assert.Equal("Kowalski", updated!.LastName);
        Assert.Equal("Inaktiv", updated.Status);
        Assert.Equal("Neue Straße 2", updated.Street);
        Assert.Equal("54321", updated.PostalCode);
        Assert.Equal("Neustadt", updated.City);
        Assert.Equal("Teilzeit", updated.EmploymentType);
        Assert.Equal("Pflegehelferin", updated.Qualification);
        Assert.Equal("Markus Berggötz", updated.EmergencyContactName);
        Assert.NotNull(updated.UpdatedAt);
    }

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

        var updated = await sut.UpdateAsync(Guid.NewGuid(), new Employee { FirstName = "X", LastName = "Y" });

        Assert.Null(updated);
    }

    [Fact]
    public async Task GetPagedAsync_FiltersBySearchStatusAndEmploymentType_AndPaginates()
    {
        var repository = new FakeEmployeeRepository();
        var sut = new EmployeeService(repository, CreatePermissionService(), new FakeCurrentUserService());

        await sut.CreateAsync(new Employee { FirstName = "Anna", LastName = "Beispiel", Status = "Aktiv", EmploymentType = "Vollzeit" });
        await sut.CreateAsync(new Employee { FirstName = "Bernd", LastName = "Muster", Status = "Aktiv", EmploymentType = "Teilzeit" });
        await sut.CreateAsync(new Employee { FirstName = "Carla", LastName = "Beispiel", Status = "Urlaub", EmploymentType = "Vollzeit" });

        var (items, totalCount) = await sut.GetPagedAsync(search: "beispiel", status: "Aktiv", employmentType: "Vollzeit", page: 1, pageSize: 10);

        Assert.Equal(1, totalCount);
        Assert.Single(items);
        Assert.Equal("Anna", items[0].FirstName);
    }

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

        await sut.CreateAsync(new Employee { FirstName = "Anna", LastName = "Eins" });
        await sut.CreateAsync(new Employee { FirstName = "Bernd", LastName = "Zwei" });
        await sut.CreateAsync(new Employee { FirstName = "Carla", LastName = "Drei" });

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

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

    [Fact]
    public async Task HasPermissionAsync_WithoutEmployeesCreatePermission_ReturnsFalse()
    {
        var role = new Role { Name = "Recruiting" };
        var user = new User
        {
            Username = "sascha",
            PasswordHash = "hashed:secret",
            IsActive = true,
            RoleId = role.Id,
            Role = role
        };

        var permissionService = new PermissionService(new FakeUserRepository(user), new MemoryCache(new MemoryCacheOptions()));

        var allowed = await permissionService.HasPermissionAsync(user.Id, ModuleType.Employees, PermissionAction.Create);

        Assert.False(allowed);
    }

    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.Employees, 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_ReturnsOnlyLinkedEmployee()
    {
        var repository = new FakeEmployeeRepository();
        var (user, users) = CreateOwnScopedAussendienstUser(linkedEmployeeId: null);
        var permissionService = new PermissionService(users, new MemoryCache(new MemoryCacheOptions()));
        var currentUser = new FakeCurrentUserService { UserId = user.Id };
        var sut = new EmployeeService(repository, permissionService, currentUser);

        var own = await sut.CreateAsync(new Employee { FirstName = "Anna", LastName = "Eigen" });
        await sut.CreateAsync(new Employee { FirstName = "Bernd", LastName = "Fremd" });
        currentUser.EmployeeId = own.Id;

        var (items, totalCount) = await sut.GetPagedAsync(search: null, status: null, employmentType: null, page: 1, pageSize: 10);

        Assert.Equal(1, totalCount);
        Assert.Equal(own.Id, Assert.Single(items).Id);
    }

    [Fact]
    public async Task GetPagedAsync_OwnScopeWithoutLinkedEmployee_ReturnsNothing()
    {
        var repository = new FakeEmployeeRepository();
        var (user, users) = CreateOwnScopedAussendienstUser(linkedEmployeeId: null);
        var permissionService = new PermissionService(users, new MemoryCache(new MemoryCacheOptions()));
        var currentUser = new FakeCurrentUserService { UserId = user.Id };
        var sut = new EmployeeService(repository, permissionService, currentUser);

        await sut.CreateAsync(new Employee { FirstName = "Bernd", LastName = "Fremd" });

        var (items, totalCount) = await sut.GetPagedAsync(search: null, status: null, employmentType: null, page: 1, pageSize: 10);

        Assert.Equal(0, totalCount);
        Assert.Empty(items);
    }

    [Fact]
    public async Task GetByIdAsync_OwnScope_ForeignEmployee_ReturnsNull()
    {
        var repository = new FakeEmployeeRepository();
        var (user, users) = CreateOwnScopedAussendienstUser(linkedEmployeeId: null);
        var permissionService = new PermissionService(users, new MemoryCache(new MemoryCacheOptions()));
        var currentUser = new FakeCurrentUserService { UserId = user.Id };
        var sut = new EmployeeService(repository, permissionService, currentUser);

        var own = await sut.CreateAsync(new Employee { FirstName = "Anna", LastName = "Eigen" });
        var foreign = await sut.CreateAsync(new Employee { FirstName = "Bernd", LastName = "Fremd" });
        currentUser.EmployeeId = own.Id;

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