using Microsoft.EntityFrameworkCore;
using ROLAC.API.Data;
using ROLAC.API.DTOs.Payee;
using ROLAC.API.Entities;
using ROLAC.API.Services.Security;
using ROLAC.API.Services.Storage;

namespace ROLAC.API.Services;

public class Payee1099Service : IPayee1099Service
{
    private readonly AppDbContext _db;
    private readonly ITinProtector _tin;
    private readonly IFileStorage _storage;

    public Payee1099Service(AppDbContext db, ITinProtector tin, IFileStorage storage)
    {
        _db      = db;
        _tin     = tin;
        _storage = storage;
    }

    public async Task<List<Payee1099ListItemDto>> GetAllAsync(bool includeInactive)
    {
        var q = _db.Payee1099s.AsNoTracking().Include(p => p.Member).AsQueryable();
        if (!includeInactive) q = q.Where(p => p.IsActive);
        return await q.OrderBy(p => p.LegalName).Select(p => new Payee1099ListItemDto
        {
            Id               = p.Id,
            LegalName        = p.LegalName,
            DisplayName      = p.DisplayName,
            MemberId         = p.MemberId,
            MemberName       = p.Member != null ? p.Member.FirstName_en + " " + p.Member.LastName_en : null,
            TaxClassification = p.TaxClassification,
            Is1099Tracked    = p.Is1099Tracked,
            TinType          = p.TinType,
            TinLast4         = p.TinLast4,
            W9Status         = p.W9Status,
            IsActive         = p.IsActive,
        }).ToListAsync();
    }

    public async Task<Payee1099Dto?> GetByIdAsync(int id)
    {
        var p = await _db.Payee1099s.AsNoTracking().Include(x => x.Member).FirstOrDefaultAsync(x => x.Id == id);
        if (p is null) return null;
        return new Payee1099Dto
        {
            Id               = p.Id,
            LegalName        = p.LegalName,
            DisplayName      = p.DisplayName,
            MemberId         = p.MemberId,
            MemberName       = p.Member != null ? $"{p.Member.FirstName_en} {p.Member.LastName_en}" : null,
            TaxClassification = p.TaxClassification,
            Is1099Tracked    = p.Is1099Tracked,
            TinType          = p.TinType,
            TinLast4         = p.TinLast4,
            W9Status         = p.W9Status,
            IsActive         = p.IsActive,
            AddressLine1     = p.AddressLine1,
            AddressLine2     = p.AddressLine2,
            City             = p.City,
            State            = p.State,
            Zip              = p.Zip,
            Email            = p.Email,
            Phone            = p.Phone,
            W9ReceivedDate   = p.W9ReceivedDate?.ToString("yyyy-MM-dd"),
            HasW9Document    = p.W9BlobPath != null,
            Notes            = p.Notes,
        };
    }

    public async Task<int> CreateAsync(SavePayee1099Request r)
    {
        var p = new Payee1099();
        Apply(p, r);
        _db.Payee1099s.Add(p);
        await _db.SaveChangesAsync();
        return p.Id;
    }

    public async Task UpdateAsync(int id, SavePayee1099Request r)
    {
        var p = await _db.Payee1099s.FirstOrDefaultAsync(x => x.Id == id)
            ?? throw new KeyNotFoundException($"Payee1099 {id} not found.");
        Apply(p, r);
        await _db.SaveChangesAsync();
    }

    public async Task DeleteAsync(int id)
    {
        var p = await _db.Payee1099s.FirstOrDefaultAsync(x => x.Id == id)
            ?? throw new KeyNotFoundException($"Payee1099 {id} not found.");
        p.IsDeleted  = true;
        p.DeletedAt  = DateTimeOffset.UtcNow;
        await _db.SaveChangesAsync();
    }

    public async Task<string?> RevealTinAsync(int id)
    {
        var p = await _db.Payee1099s.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id);
        return p?.TinEncrypted is null ? null : _tin.Unprotect(p.TinEncrypted);
    }

    public async Task SaveW9Async(int id, Stream content, string fileName)
    {
        var p = await _db.Payee1099s.FirstOrDefaultAsync(x => x.Id == id)
            ?? throw new KeyNotFoundException($"Payee1099 {id} not found.");

        // Mirror the expense-receipt blob convention: a stable per-record path under a feature folder,
        // preserving the original extension. Re-uploads overwrite the prior blob.
        var ext  = Path.GetExtension(fileName);
        var path = $"finance/w9/{p.Id}{ext}";
        if (p.W9BlobPath != null && p.W9BlobPath != path)
            await _storage.DeleteAsync(p.W9BlobPath);
        var saved = await _storage.SaveAsync(content, path);
        p.W9BlobPath = saved;
        await _db.SaveChangesAsync();
    }

    public async Task<(Stream stream, string contentType)?> OpenW9Async(int id)
    {
        var p = await _db.Payee1099s.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id);
        if (p?.W9BlobPath is null) return null;
        var stream = await _storage.OpenReadAsync(p.W9BlobPath);
        if (stream is null) return null;
        var ext = Path.GetExtension(p.W9BlobPath).ToLowerInvariant();
        var contentType = ext switch
        {
            ".png" => "image/png", ".webp" => "image/webp", ".pdf" => "application/pdf",
            _ => "image/jpeg",
        };
        return (stream, contentType);
    }

    // Maps request fields onto the entity. A null/blank Tin leaves the existing ciphertext untouched (update case).
    private void Apply(Payee1099 p, SavePayee1099Request r)
    {
        p.LegalName        = r.LegalName;
        p.DisplayName      = r.DisplayName;
        p.MemberId         = r.MemberId;
        p.TaxClassification = r.TaxClassification;
        p.Is1099Tracked    = r.Is1099Tracked;
        p.TinType          = r.TinType;
        p.AddressLine1     = r.AddressLine1;
        p.AddressLine2     = r.AddressLine2;
        p.City             = r.City;
        p.State            = r.State;
        p.Zip              = r.Zip;
        p.Email            = r.Email;
        p.Phone            = r.Phone;
        p.W9Status         = r.W9Status;
        p.W9ReceivedDate   = r.W9ReceivedDate;
        p.IsActive         = r.IsActive;
        p.Notes            = r.Notes;
        if (!string.IsNullOrWhiteSpace(r.Tin))
        {
            p.TinEncrypted = _tin.Protect(r.Tin);
            p.TinLast4     = TinProtector.Last4(r.Tin);
        }
    }
}
