using Baya.Application.Contracts.Common;
using Baya.Application.Contracts.Configuration;
using Baya.Domain.Entities.Booking;
using Baya.Domain.Entities.Catalog;
using Baya.Domain.Entities.Geography;
using Baya.Domain.Entities.Identity;
using Baya.Domain.Entities.User;
using Baya.Infrastructure.Persistence;
using Baya.Infrastructure.Persistence.Repositories.Common;
using Baya.Tests.Setup.Setups;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using NSubstitute;
using BookingEntity = Baya.Domain.Entities.Booking.Booking;

namespace Baya.Test.Foundation.Bookings;

/// <summary>
/// A self-contained SQLite host exercising the real EF model (schema, filtered indexes, CHECK, query
/// filters, HasData seeds incl. the cancellation-policy tiers) for the b9 booking engine. Seeds one bookable
/// nurse and one customer (with a geocoded address), and lets a test create an accepted request, convert it,
/// and drive the real <see cref="UnitOfWork"/> against real handlers with substituted seams.
/// </summary>
public sealed class BookingsTestHost : 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; }

    // The seeded address coordinates — the EVV distance check resolves the booking address here.
    public decimal AddressLat => 35.6892m;
    public decimal AddressLng => 51.3890m;

    private readonly long _cityId;

    public BookingsTestHost()
    {
        _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();
        _cityId = city.Id;

        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 = AddressLat, Longitude = AddressLng, 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;
    }

    public long CategoryId { get; }

    /// <summary>Adds a bookable variant; <paramref name="sessionCount"/> null = single visit.</summary>
    public long AddVariant(int? sessionCount, long price = 5_000_000)
    {
        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;
    }

    /// <summary>Creates an <c>accepted_awaiting_payment</c> request against the given variant.</summary>
    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;
    }

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

    public ICurrentUser AsNurse(int? userId = null)
    {
        var u = Substitute.For<ICurrentUser>();
        u.UserId.Returns(userId ?? NurseUserId);
        u.Roles.Returns(new[] { RoleNames.Nurse });
        return u;
    }

    public ICurrentUser AsAdmin(int userId = 9999)
    {
        var u = Substitute.For<ICurrentUser>();
        u.UserId.Returns(userId);
        u.Roles.Returns(new[] { RoleNames.Admin });
        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, int disputeWindowHours = 72, int evvToleranceMeters = 200, int noShowThresholdMinutes = 60)
    {
        var cfg = Substitute.For<IPlatformConfig>();
        cfg.GetConfig<decimal>("platform_fee_rate", Arg.Any<CancellationToken>()).Returns(feeRate);
        cfg.GetConfig<int>("dispute_window_hours", Arg.Any<CancellationToken>()).Returns(disputeWindowHours);
        cfg.GetConfig<int>("evv_location_tolerance_meters", Arg.Any<CancellationToken>()).Returns(evvToleranceMeters);
        cfg.GetConfig<int>("no_show_threshold_minutes", Arg.Any<CancellationToken>()).Returns(noShowThresholdMinutes);
        return cfg;
    }

    public IPaymentCaptureSimulator Capture(bool succeeds = true, long? pspFee = 12_000)
    {
        var c = Substitute.For<IPaymentCaptureSimulator>();
        c.ConfirmCaptureAsync(Arg.Any<long>(), Arg.Any<CancellationToken>())
            .Returns(new PaymentCaptureResult(succeeds, succeeds ? "ref" : string.Empty, succeeds ? pspFee : null));
        return c;
    }

    public string StatusOf(long bookingId)
        => Db.Set<BookingEntity>().AsNoTracking().Where(b => b.Id == bookingId).Select(b => b.Status).Single();

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