using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Moq;
using ROLAC.API.Data;
using ROLAC.API.Data.Interceptors;
using ROLAC.API.DTOs.Members;
using ROLAC.API.Entities;
using ROLAC.API.Services;
using Xunit;

namespace ROLAC.API.Tests.Services;

public class MemberServiceTests
{
    private static IHttpContextAccessor BuildAccessor(string userId = "test-user")
    {
        var claims  = new[] { new Claim(ClaimTypes.NameIdentifier, userId) };
        var ctx     = new DefaultHttpContext { User = new(new ClaimsIdentity(claims)) };
        var mock    = new Mock<IHttpContextAccessor>();
        mock.Setup(x => x.HttpContext).Returns(ctx);
        return mock.Object;
    }

    /// <summary>
    /// Builds an InMemory AppDbContext that includes the AuditSaveChangesInterceptor
    /// so that CreatedBy/UpdatedBy are stamped on save (required by InMemory null checks).
    /// </summary>
    private static AppDbContext BuildDb(string userId = "test-user")
    {
        var accessor     = BuildAccessor(userId);
        var interceptor  = new AuditSaveChangesInterceptor(new ROLAC.API.Services.Logging.CurrentUserAccessor(accessor));
        return new AppDbContext(
            new DbContextOptionsBuilder<AppDbContext>()
                .UseInMemoryDatabase(Guid.NewGuid().ToString())
                .AddInterceptors(interceptor)
                .Options);
    }

    // ── Create ───────────────────────────────────────────────────────────────

    [Fact]
    public async Task CreateAsync_ReturnsNewId()
    {
        using var db  = BuildDb();
        var svc       = new MemberService(db, BuildAccessor());
        var request   = new CreateMemberRequest { FirstName_en = "Chris", LastName_en = "Chen" };

        var id = await svc.CreateAsync(request);

        Assert.True(id > 0);
        var saved = await db.Members.FindAsync(id);
        Assert.NotNull(saved);
        Assert.Equal("Chris", saved.FirstName_en);
    }

    [Fact]
    public async Task CreateAsync_SavesNickName()
    {
        using var db  = BuildDb();
        var svc       = new MemberService(db, BuildAccessor());
        var request   = new CreateMemberRequest
            { FirstName_en = "Yuan", LastName_en = "Chen", NickName = "Chris" };

        var id   = await svc.CreateAsync(request);
        var saved = await db.Members.FindAsync(id);

        Assert.Equal("Chris", saved!.NickName);
    }

    // ── GetById ──────────────────────────────────────────────────────────────

    [Fact]
    public async Task GetByIdAsync_ReturnsDto_WhenExists()
    {
        using var db  = BuildDb();
        var svc       = new MemberService(db, BuildAccessor());
        var id        = await svc.CreateAsync(
            new CreateMemberRequest { FirstName_en = "A", LastName_en = "B" });

        var dto = await svc.GetByIdAsync(id);

        Assert.NotNull(dto);
        Assert.Equal(id, dto.Id);
        Assert.Equal("A", dto.FirstName_en);
    }

    [Fact]
    public async Task GetByIdAsync_ReturnsNull_WhenNotFound()
    {
        using var db  = BuildDb();
        var svc       = new MemberService(db, BuildAccessor());

        var dto = await svc.GetByIdAsync(9999);

        Assert.Null(dto);
    }

    // ── GetPaged ─────────────────────────────────────────────────────────────

    [Fact]
    public async Task GetPagedAsync_FiltersOnSearch()
    {
        using var db  = BuildDb();
        var svc       = new MemberService(db, BuildAccessor());
        await svc.CreateAsync(new CreateMemberRequest { FirstName_en = "Chris", LastName_en = "Chen" });
        await svc.CreateAsync(new CreateMemberRequest { FirstName_en = "Alice", LastName_en = "Wang" });

        var result = await svc.GetPagedAsync(1, 20, "Chris", null, null);

        Assert.Equal(1, result.TotalCount);
        Assert.Equal("Chris", result.Items[0].FirstName_en);
    }

    [Fact]
    public async Task GetPagedAsync_FiltersOnStatus()
    {
        using var db  = BuildDb();
        var svc       = new MemberService(db, BuildAccessor());
        await svc.CreateAsync(new CreateMemberRequest
            { FirstName_en = "A", LastName_en = "A", Status = "Member" });
        await svc.CreateAsync(new CreateMemberRequest
            { FirstName_en = "B", LastName_en = "B", Status = "Visitor" });

        var result = await svc.GetPagedAsync(1, 20, null, "Visitor", null);

        Assert.Equal(1, result.TotalCount);
        Assert.Equal("Visitor", result.Items[0].Status);
    }

    [Fact]
    public async Task GetPagedAsync_SearchesNickName()
    {
        using var db  = BuildDb();
        var svc       = new MemberService(db, BuildAccessor());
        await svc.CreateAsync(new CreateMemberRequest
            { FirstName_en = "Yuan", LastName_en = "Chen", NickName = "Chris" });

        var result = await svc.GetPagedAsync(1, 20, "Chris", null, null);

        Assert.Equal(1, result.TotalCount);
    }

    // ── Update ───────────────────────────────────────────────────────────────

    [Fact]
    public async Task UpdateAsync_PersistsChanges()
    {
        using var db  = BuildDb();
        var svc       = new MemberService(db, BuildAccessor());
        var id        = await svc.CreateAsync(
            new CreateMemberRequest { FirstName_en = "Old", LastName_en = "Name" });

        await svc.UpdateAsync(id, new UpdateMemberRequest
            { FirstName_en = "New", LastName_en = "Name", Country = "USA",
              Status = "Member", LanguagePreference = "en" });

        var saved = await db.Members.FindAsync(id);
        Assert.Equal("New", saved!.FirstName_en);
    }

    [Fact]
    public async Task UpdateAsync_ThrowsKeyNotFound_WhenMissing()
    {
        using var db  = BuildDb();
        var svc       = new MemberService(db, BuildAccessor());

        await Assert.ThrowsAsync<KeyNotFoundException>(() =>
            svc.UpdateAsync(9999, new UpdateMemberRequest
                { FirstName_en = "X", LastName_en = "Y", Country = "USA",
                  Status = "Member", LanguagePreference = "en" }));
    }

    // ── Delete (soft) ────────────────────────────────────────────────────────

    [Fact]
    public async Task DeleteAsync_SoftDeletesMember()
    {
        using var db  = BuildDb();
        var svc       = new MemberService(db, BuildAccessor("deleter-id"));
        var id        = await svc.CreateAsync(
            new CreateMemberRequest { FirstName_en = "A", LastName_en = "B" });

        await svc.DeleteAsync(id);

        // Query-filtered view returns null
        var filtered = await db.Members.FindAsync(id);
        Assert.Null(filtered);

        // Raw view shows IsDeleted = true
        var raw = await db.Members.IgnoreQueryFilters()
            .FirstAsync(m => m.Id == id);
        Assert.True(raw.IsDeleted);
        Assert.Equal("deleter-id", raw.DeletedBy);
        Assert.NotNull(raw.DeletedAt);
    }

    [Fact]
    public async Task DeleteAsync_ThrowsKeyNotFound_WhenMissing()
    {
        using var db  = BuildDb();
        var svc       = new MemberService(db, BuildAccessor());

        await Assert.ThrowsAsync<KeyNotFoundException>(() => svc.DeleteAsync(9999));
    }
}
