using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Moq;
using System.Security.Claims;
using ROLAC.API.Data;
using ROLAC.API.Data.Interceptors;
using ROLAC.API.DTOs.Payee;
using ROLAC.API.Entities;
using ROLAC.API.Services;
using ROLAC.API.Services.Logging;
using ROLAC.API.Services.Security;
using ROLAC.API.Services.Storage;
using Xunit;

namespace ROLAC.API.Tests.Services;

public class Payee1099ServiceTests
{
    // Minimal in-memory IFileStorage (mirrors the ExpenseServiceTests fake).
    private sealed class FakeStorage : IFileStorage
    {
        public Dictionary<string, byte[]> Files = new();
        public Task<string> SaveAsync(Stream c, string p, CancellationToken ct = default)
        { using var ms = new MemoryStream(); c.CopyTo(ms); Files[p] = ms.ToArray(); return Task.FromResult(p); }
        public Task<Stream?> OpenReadAsync(string p, CancellationToken ct = default)
        => Task.FromResult<Stream?>(Files.TryGetValue(p, out var b) ? new MemoryStream(b) : null);
        public Task DeleteAsync(string p, CancellationToken ct = default) { Files.Remove(p); return Task.CompletedTask; }
    }

    private static (Payee1099Service svc, AppDbContext db) Build()
    {
        var httpContext = new DefaultHttpContext { User = new(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, "test-user") })) };
        var accessorMock = new Mock<IHttpContextAccessor>();
        accessorMock.Setup(x => x.HttpContext).Returns(httpContext);
        var db = new AppDbContext(new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .AddInterceptors(new AuditSaveChangesInterceptor(new CurrentUserAccessor(accessorMock.Object))).Options);
        var tin = new TinProtector(DataProtectionProvider.Create("ROLAC.Tests"));
        return (new Payee1099Service(db, tin, new FakeStorage()), db);
    }

    [Fact]
    public async Task Create_encrypts_tin_and_stores_last4_only_in_clear()
    {
        var (svc, db) = Build();
        var id = await svc.CreateAsync(new SavePayee1099Request
        { LegalName = "Pat Player", TinType = "SSN", Tin = "123-45-6789", W9Status = "OnFile" });

        var saved = await db.Payee1099s.FindAsync(id);
        Assert.NotNull(saved);
        Assert.Equal("6789", saved!.TinLast4);
        Assert.NotNull(saved.TinEncrypted);
        Assert.DoesNotContain("123-45-6789", saved.TinEncrypted!);
        Assert.Equal("123-45-6789", await svc.RevealTinAsync(id));
    }

    [Fact]
    public async Task Update_with_null_tin_keeps_existing_ciphertext()
    {
        var (svc, db) = Build();
        var id = await svc.CreateAsync(new SavePayee1099Request { LegalName = "X", Tin = "11-2223333" });
        var before = (await db.Payee1099s.FindAsync(id))!.TinEncrypted;

        await svc.UpdateAsync(id, new SavePayee1099Request { LegalName = "X renamed", Tin = null });

        var after = await db.Payee1099s.FindAsync(id);
        Assert.Equal("X renamed", after!.LegalName);
        Assert.Equal(before, after.TinEncrypted);
        Assert.Equal("3333", after.TinLast4);
    }

    [Fact]
    public async Task List_dto_masks_tin_to_last4()
    {
        var (svc, _) = Build();
        await svc.CreateAsync(new SavePayee1099Request { LegalName = "Y", Tin = "999-88-7777" });
        var list = await svc.GetAllAsync(includeInactive: true);
        var item = Assert.Single(list);
        Assert.Equal("7777", item.TinLast4);
    }

    [Fact]
    public async Task Delete_is_soft_and_hides_from_list()
    {
        var (svc, _) = Build();
        var id = await svc.CreateAsync(new SavePayee1099Request { LegalName = "Z" });
        await svc.DeleteAsync(id);
        Assert.Empty(await svc.GetAllAsync(includeInactive: true));
    }

    [Fact]
    public async Task SaveW9_records_document_and_round_trips_bytes()
    {
        var (svc, _) = Build();
        var id = await svc.CreateAsync(new SavePayee1099Request { LegalName = "W9 Payee" });

        var bytes = new byte[] { 1, 2, 3, 4, 5 };
        await svc.SaveW9Async(id, new MemoryStream(bytes), "w9.pdf");

        var dto = await svc.GetByIdAsync(id);
        Assert.NotNull(dto);
        Assert.True(dto!.HasW9Document);

        var opened = await svc.OpenW9Async(id);
        Assert.NotNull(opened);
        Assert.Equal("application/pdf", opened!.Value.contentType);
        using var ms = new MemoryStream();
        await opened.Value.stream.CopyToAsync(ms);
        Assert.Equal(bytes, ms.ToArray());
    }
}
