using Baya.Application.Common;
using Baya.Application.Contracts.Common;
using Baya.Application.Contracts.Configuration;
using Baya.Application.Contracts.Payments;
using Baya.Domain.Entities.Booking;
using Baya.Domain.Entities.Bnpl;
using Baya.Domain.Entities.Catalog;
using Baya.Domain.Entities.Geography;
using Baya.Domain.Entities.Identity;
using Baya.Domain.Entities.Payments;
using Baya.Domain.Entities.User;
using Baya.Infrastructure.CrossCutting.Seams;
using Baya.Infrastructure.Persistence;
using Baya.Infrastructure.Persistence.Repositories.Common;
using Baya.Tests.Setup.Setups;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using NSubstitute;

namespace Baya.Test.Foundation.Bnpl;

/// <summary>
/// A self-contained SQLite host exercising the real EF model (the strict 1:1 <c>UNIQUE(payment_transaction_id)</c>,
/// the settle-split CHECK, the append-only ledger) for the b12 BNPL engine. Seeds one bookable nurse + one
/// customer + an active BNPL gateway, and can seed a verified BNPL order so a test can drive the real settle
/// handler against the real <see cref="UnitOfWork"/> with a deterministic provider mock.
/// </summary>
public sealed class BnplTestHost : IDisposable
{
    private readonly SqliteConnection _connection;
    public ApplicationDbContext Db { get; }
    public UnitOfWork UnitOfWork { get; }

    public long CustomerId { get; }
    public int CustomerUserId { get; }
    public long NurseId { get; }
    public int NurseUserId { get; }
    public long PatientId { get; }
    public long AddressId { get; }
    public long CategoryId { get; }
    public long GatewayId { get; }

    public BnplTestHost()
    {
        _connection = new SqliteConnection("DataSource=:memory:");
        _connection.Open();

        var options = new DbContextOptionsBuilder<ApplicationDbContext>().UseSqlite(_connection).Options;
        Db = new ApplicationDbContext(options, TestFieldEncryptor.Instance);
        Db.Database.EnsureCreated();
        UnitOfWork = new UnitOfWork(Db);

        var province = new Province { NameFa = "تهران", NameEn = "Tehran", SortOrder = 1, IsActive = true };
        Db.Set<Province>().Add(province);
        Db.SaveChanges();
        var city = new City { ProvinceId = province.Id, NameFa = "تهران", NameEn = "Tehran", SortOrder = 1, IsActive = true };
        Db.Set<City>().Add(city);
        var category = new ServiceCategory { NameFa = "سالمند", NameEn = "Elderly", SortOrder = 1, IsActive = true };
        Db.Set<ServiceCategory>().Add(category);
        Db.SaveChanges();
        CategoryId = category.Id;

        var customerUser = new User { UserName = "cust1", PhoneNumber = "09120000001", Gender = "male", Name = "علی", FamilyName = "رضایی", IsActive = true };
        Db.Users.Add(customerUser);
        Db.SaveChanges();
        CustomerUserId = customerUser.Id;
        var customer = new CustomerProfile { UserId = customerUser.Id };
        Db.Set<CustomerProfile>().Add(customer);
        Db.SaveChanges();
        CustomerId = customer.Id;

        var patient = new Patient { CustomerId = customer.Id, DisplayName = "پدر", FirstName = "حسن", LastName = "رضایی", Gender = "male", IsActive = true };
        Db.Set<Patient>().Add(patient);
        var address = new CustomerAddress
        {
            CustomerId = customer.Id, CityId = city.Id, Title = "خانه", AddressLine = "خیابان اول",
            PostalCode = "1111111111", RecipientName = "علی", RecipientPhone = "09120000001",
            Latitude = 35.6892m, Longitude = 51.3890m, IsPrimary = true
        };
        Db.Set<CustomerAddress>().Add(address);
        Db.SaveChanges();
        PatientId = patient.Id;
        AddressId = address.Id;

        var nurseUser = new User { UserName = "nurse1", PhoneNumber = "09120000002", Gender = "female", Name = "زهرا", FamilyName = "احمدی", IsActive = true };
        Db.Users.Add(nurseUser);
        Db.SaveChanges();
        NurseUserId = nurseUser.Id;
        var nurse = new NurseProfile { UserId = nurseUser.Id };
        nurse.MarkVerified();
        nurse.SetAcceptingBookings(true);
        Db.Set<NurseProfile>().Add(nurse);
        Db.SaveChanges();
        NurseId = nurse.Id;

        var gateway = new PaymentGateway
        {
            ProviderCode = BnplProviderCodes.SnappPay, Type = PaymentGatewayType.Bnpl, DisplayName = "SnappPay",
            ConfigJson = "{\"merchantId\":\"test\",\"sandbox\":true}", IsActive = true, Priority = 0
        };
        Db.Set<PaymentGateway>().Add(gateway);
        Db.SaveChanges();
        GatewayId = gateway.Id;
    }

    public long AddAcceptedRequest(long price)
    {
        var variant = new NurseServiceVariant
        {
            NurseId = NurseId, ServiceCategoryId = CategoryId, Price = price, PriceUnit = "per_day",
            SessionCount = null, DisplayName = "مراقبت روزانه", OptionSetHash = $"hash-{Guid.NewGuid():N}", IsActive = true
        };
        Db.Set<NurseServiceVariant>().Add(variant);
        Db.SaveChanges();

        var request = new BookingRequest
        {
            CustomerId = CustomerId, NurseId = NurseId, PatientId = PatientId, VariantId = variant.Id,
            CustomerAddressId = AddressId, RequiredCaregiverGender = CaregiverGender.Any,
            RequestedDate = new DateOnly(2026, 8, 1), RequestedTimeStart = new TimeOnly(9, 0), RequestedTimeEnd = new TimeOnly(13, 0),
            CustomerNotes = "note", NurseResponseDeadlineAt = new DateTime(2030, 1, 1, 0, 0, 0, DateTimeKind.Utc)
        };
        Db.Set<BookingRequest>().Add(request);
        Db.SaveChanges();
        request.Accept(new DateTime(2030, 1, 1, 0, 0, 0, DateTimeKind.Utc));
        Db.SaveChanges();
        return request.Id;
    }

    /// <summary>Seeds a verified BNPL order (its pending payment_transaction + the 1:1 bnpl_transactions row walked
    /// eligible → token_issued → verified) against a fresh accepted request. Returns (bnplId, requestId, token).</summary>
    public (long BnplId, long RequestId, string Token) SeedVerifiedOrder(long gross)
    {
        var requestId = AddAcceptedRequest(gross);
        var token = $"tok-{Guid.NewGuid():N}";

        var transaction = new PaymentTransaction
        {
            BookingRequestId = requestId, CustomerId = CustomerId, GatewayId = GatewayId,
            Amount = gross, Currency = "IRR", IsInstallment = true, GatewayReferenceCode = token
        };
        Db.Set<PaymentTransaction>().Add(transaction);
        Db.SaveChanges();

        var bnpl = new BnplTransaction
        {
            PaymentTransactionId = transaction.Id, ProviderCode = BnplProviderCodes.SnappPay,
            MerchantOfRecord = "platform", OrderAmountIrr = gross, Currency = "IRR"
        };
        bnpl.EligibilityStatus = BnplEligibilityStatus.Eligible;
        bnpl.MarkTokenIssued(token, "order-1");
        bnpl.MarkVerified("verify-1", null);
        Db.Set<BnplTransaction>().Add(bnpl);
        Db.SaveChanges();

        return (bnpl.Id, requestId, token);
    }

    // ---- seams ----
    public IVariantSnapshotSerializer Serializer { get; } = new VariantSnapshotSerializer();
    public ISettlementSplitProvider Settlement { get; } = new MockSettlementSplitProvider();
    public IDistributedLock Lock { get; } = new InProcessDistributedLock();
    public ICurrencyNormalizer Currency { get; } = new MockCurrencyNormalizer(Options.Create(new SeamOptions()));

    public IBnplProviderResolver Resolver(decimal commissionRate = 0.10m, bool forceFailure = false)
    {
        var opts = Options.Create(new SeamOptions { Bnpl = new BnplOptions { CommissionRate = commissionRate, ForceFailure = forceFailure } });
        return new MockBnplProviderResolver(new MockBnplProvider(opts));
    }

    public ICurrentUser AsCustomer()
    {
        var u = Substitute.For<ICurrentUser>();
        u.UserId.Returns(CustomerUserId);
        u.Roles.Returns(new[] { RoleNames.Customer });
        return u;
    }

    public IDateTimeProvider Clock(DateTimeOffset now)
    {
        var c = Substitute.For<IDateTimeProvider>();
        c.UtcNow.Returns(now);
        return c;
    }

    public IPlatformConfig Config(decimal feeRate = 0.15m)
    {
        var cfg = Substitute.For<IPlatformConfig>();
        cfg.GetConfig<decimal>("platform_fee_rate", Arg.Any<CancellationToken>()).Returns(feeRate);
        cfg.GetConfig<string>("bnpl_merchant_of_record", Arg.Any<CancellationToken>()).Returns("platform");
        return cfg;
    }

    public INotificationDispatcher Notifications() => Substitute.For<INotificationDispatcher>();

    public IReadOnlyList<LedgerEntry> LedgerFor(long bookingId)
        => Db.Set<LedgerEntry>().AsNoTracking().Where(l => l.BookingId == bookingId).OrderBy(l => l.Id).ToList();

    public BnplTransaction ReloadBnpl(long id)
        => Db.Set<BnplTransaction>().AsNoTracking().Single(b => b.Id == id);

    public void Dispose()
    {
        Db.Dispose();
        _connection.Dispose();
    }
}
