#nullable enable
using Baya.Application.Contracts.Common;
using Baya.Application.Contracts.Configuration;
using Baya.Application.Contracts.Reviews;
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.Reviews;

/// <summary>
/// A self-contained SQLite host exercising the real EF model (schema, CHECK, unique indexes, seed) for the b14
/// reviews + patient-care-records engine. Seeds one bookable nurse, a second (unassigned) nurse, one customer +
/// patient + address, and can create a completed booking so a test can drive the real handlers against the real
/// <see cref="UnitOfWork"/> with substituted seams.
/// </summary>
public sealed class ReviewsTestHost : 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 OtherNurseId { get; }
    public int OtherNurseUserId { get; }
    public long PatientId { get; }

    private readonly long _cityId;
    private readonly long _categoryId;
    private readonly long _addressId;

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

        (NurseId, NurseUserId) = SeedNurse("nurse1", "09120000002");
        (OtherNurseId, OtherNurseUserId) = SeedNurse("nurse2", "09120000003");
    }

    private (long NurseId, int UserId) SeedNurse(string userName, string phone)
    {
        var nurseUser = new User { UserName = userName, PhoneNumber = phone, Gender = "female", Name = "زهرا", FamilyName = "احمدی", IsActive = true };
        Db.Users.Add(nurseUser);
        Db.SaveChanges();
        var nurse = new NurseProfile { UserId = nurseUser.Id };
        nurse.MarkVerified();
        nurse.SetAcceptingBookings(true);
        Db.Set<NurseProfile>().Add(nurse);
        Db.SaveChanges();
        return (nurse.Id, nurseUser.Id);
    }

    /// <summary>Seeds a booking for the seeded nurse + patient in the given status (default completed).</summary>
    public long SeedBooking(string status = BookingStatus.Completed, long? nurseId = null)
    {
        var owningNurse = nurseId ?? NurseId;
        var variant = new NurseServiceVariant
        {
            NurseId = owningNurse, ServiceCategoryId = _categoryId, Price = 10_000_000, 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 = owningNurse, 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();

        var booking = new BookingEntity
        {
            BookingRequestId = request.Id, CustomerId = CustomerId, NurseId = owningNurse, PatientId = PatientId,
            VariantId = variant.Id, CustomerAddressId = _addressId, VariantSnapshotJson = "{}", AddressSnapshotJson = "{}",
            GrossPriceIrr = 10_000_000, BalinyaarCommissionIrr = 1_500_000, PlatformFeeRate = 0.15m,
            NursePayoutAmount = 8_500_000, SessionCount = 1,
            ScheduledDate = new DateOnly(2026, 8, 1), ScheduledTimeStart = new TimeOnly(9, 0), ScheduledTimeEnd = new TimeOnly(13, 0)
        };
        var now = new DateTime(2026, 8, 2, 0, 0, 0, DateTimeKind.Utc);
        WalkTo(booking, status, now);
        Db.Set<BookingEntity>().Add(booking);
        Db.SaveChanges();

        return booking.Id;
    }

    private static void WalkTo(BookingEntity booking, string status, DateTime now)
    {
        if (status == BookingStatus.PendingPayment)
            return;

        booking.TransitionTo(BookingStatus.Confirmed, now);
        if (status == BookingStatus.Confirmed)
            return;

        // Cancelled is only legal from confirmed/in-progress, never from a completed booking.
        if (status == BookingStatus.Cancelled)
        {
            booking.TransitionTo(BookingStatus.Cancelled, now, actor: CancellationActor.Customer, reason: "test");
            return;
        }

        booking.TransitionTo(BookingStatus.InProgress, now);
        if (status == BookingStatus.InProgress)
            return;

        booking.TransitionTo(BookingStatus.Completed, now);
        if (status is BookingStatus.Closed or BookingStatus.Disputed)
            booking.TransitionTo(status, now);
    }

    public decimal AverageRatingOf(long nurseId)
        => Db.Set<NurseProfile>().AsNoTracking().Where(p => p.Id == nurseId).Select(p => p.AverageRating).First();

    public int TotalReviewsOf(long nurseId)
        => Db.Set<NurseProfile>().AsNoTracking().Where(p => p.Id == nurseId).Select(p => p.TotalReviews).First();

    public ICurrentUser AsCustomer() => User(CustomerUserId, RoleNames.Customer);
    public ICurrentUser AsNurse() => User(NurseUserId, RoleNames.Nurse);
    public ICurrentUser AsOtherNurse() => User(OtherNurseUserId, RoleNames.Nurse);
    public ICurrentUser AsAdmin(int userId = 9999) => User(userId, RoleNames.Admin);
    public ICurrentUser AsUser(int userId, params string[] roles) => User(userId, roles);

    private static ICurrentUser User(int userId, params string[] roles)
    {
        var u = Substitute.For<ICurrentUser>();
        u.UserId.Returns(userId);
        u.IsAuthenticated.Returns(true);
        u.Roles.Returns(roles);
        return u;
    }

    public IDateTimeProvider Clock() => Clock(new DateTimeOffset(2026, 8, 3, 10, 0, 0, TimeSpan.Zero));

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

    public IPlatformConfig Config(decimal lowRatingThreshold = 2m)
    {
        var cfg = Substitute.For<IPlatformConfig>();
        cfg.GetConfig<decimal>("min_rating_for_support_alert", Arg.Any<CancellationToken>()).Returns(lowRatingThreshold);
        return cfg;
    }

    public IReviewModerationService Moderation(ModerationDecision decision = ModerationDecision.Flag)
    {
        var m = Substitute.For<IReviewModerationService>();
        m.ScreenAsync(Arg.Any<string?>(), Arg.Any<CancellationToken>())
            .Returns(new ModerationVerdict(decision, "test"));
        return m;
    }

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