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.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.Payments;

/// <summary>
/// A self-contained SQLite host exercising the real EF model (the two filtered uniques, the append-only
/// ledger, the webhook idempotency unique) for the b10 money core. Seeds one bookable nurse + one customer
/// with a geocoded address, an active standard gateway, and lets a test create an accepted request and drive
/// the real payment handlers against the real <see cref="UnitOfWork"/> with faithful money-path seams.
/// </summary>
public sealed class PaymentsTestHost : 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 PaymentsTestHost()
    {
        _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);
        Db.SaveChanges();

        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);
        Db.SaveChanges();
        PatientId = patient.Id;

        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();
        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 = "zarinpal", Type = PaymentGatewayType.Standard, DisplayName = "ZarinPal",
            ConfigJson = "{\"merchantId\":\"test\",\"sandbox\":true}", IsActive = true, Priority = 0
        };
        Db.Set<PaymentGateway>().Add(gateway);
        Db.SaveChanges();
        GatewayId = gateway.Id;
    }

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

    public long AddAcceptedRequest(long variantId)
    {
        var request = new BookingRequest
        {
            CustomerId = CustomerId, NurseId = NurseId, PatientId = PatientId, VariantId = variantId,
            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;
    }

    // ---- seams ----
    public IVariantSnapshotSerializer Serializer { get; } = new VariantSnapshotSerializer();
    public IPaymentProvider PaymentProvider { get; } = new MockPaymentProvider();
    public ISettlementSplitProvider Settlement { get; } = new MockSettlementSplitProvider();
    public IDistributedLock Lock { get; } = new InProcessDistributedLock();
    public IWebhookVerifier Verifier { get; } = new MockWebhookVerifier(Options.Create(new SeamOptions()));

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

    public ICurrentUser AsNurse()
    {
        var u = Substitute.For<ICurrentUser>();
        u.UserId.Returns(NurseUserId);
        u.Roles.Returns(new[] { RoleNames.Nurse });
        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);
        return cfg;
    }

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

    public string StatusOfBooking(long bookingId)
        => Db.Set<Baya.Domain.Entities.Booking.Booking>().AsNoTracking().Where(b => b.Id == bookingId).Select(b => b.Status).Single();

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