using System.Net;
using System.Net.Http.Json;
using Baya.Application.Contracts.Identity;
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.Persistence;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using BookingEntity = Baya.Domain.Entities.Booking.Booking;

namespace Baya.Test.Api;

public class AdminPayoutsApiTests(BayaApiFactory factory) : IClassFixture<BayaApiFactory>
{
    [Fact]
    public async Task Generate_Unauthenticated_Returns401()
    {
        var client = factory.CreateClient();
        var response = await client.PostAsJsonAsync("/api/v1/admin_payouts/batches",
            new { periodStart = "2020-01-01", periodEnd = "2020-01-31" });
        Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
    }

    [Fact]
    public async Task Generate_InvalidPeriod_Returns400()
    {
        var admin = factory.CreateClient();
        await AdminTestClient.AuthenticateAsync(factory, admin, "09131902401");

        // period_start after period_end
        var response = await admin.PostAsJsonAsync("/api/v1/admin_payouts/batches",
            new { periodStart = "2026-06-30", periodEnd = "2026-06-01" });
        Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
    }

    [Fact]
    public async Task Generate_then_process_pays_the_eligible_nurse()
    {
        var admin = factory.CreateClient();
        await AdminTestClient.AuthenticateAsync(factory, admin, "09131902402");
        await SeedEligibleNurseBookingAsync(factory, "09131902403");

        var today = DateOnly.FromDateTime(DateTime.UtcNow).ToString("yyyy-MM-dd");
        var generate = await admin.PostAsJsonAsync("/api/v1/admin_payouts/batches",
            new { periodStart = "2020-01-01", periodEnd = today });
        Assert.Equal(HttpStatusCode.OK, generate.StatusCode);

        var data = await AuthTestClient.ReadDataAsync(generate);
        var batchId = data.GetProperty("batch").GetProperty("id").GetInt64();
        Assert.Equal("draft", data.GetProperty("batch").GetProperty("status").GetString());
        Assert.True(data.GetProperty("payouts").GetArrayLength() >= 1);

        var process = await admin.PostAsJsonAsync($"/api/v1/admin_payouts/batches/{batchId}/process", new { });
        Assert.Equal(HttpStatusCode.OK, process.StatusCode);
        var processed = await AuthTestClient.ReadDataAsync(process);
        Assert.Equal("completed", processed.GetProperty("status").GetString());
        Assert.True(processed.GetProperty("paidCount").GetInt32() >= 1);
    }

    [Fact]
    public async Task List_AsAdmin_ReturnsPagedEnvelope()
    {
        var admin = factory.CreateClient();
        await AdminTestClient.AuthenticateAsync(factory, admin, "09131902404");

        var response = await admin.GetAsync("/api/v1/admin_payouts/batches?page=1&pageSize=20");

        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var data = await AuthTestClient.ReadDataAsync(response);
        Assert.True(data.GetProperty("total").GetInt32() >= 0);
    }

    /// <summary>Seeds a completed, dispute-window-closed booking for a nurse who has a verified primary IBAN, so
    /// the batch has exactly one eligible payout. Returns the nurse's phone (usable to authenticate as the nurse).</summary>
    internal static async Task SeedEligibleNurseBookingAsync(BayaApiFactory factory, string nursePhone)
    {
        using var scope = factory.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
        var userManager = scope.ServiceProvider.GetRequiredService<IAppUserManager>();

        var customerUser = new User { UserName = $"cust_{Guid.NewGuid():N}", PhoneNumber = $"0912{Random.Shared.Next(1000000, 9999999)}", IsActive = true };
        db.Users.Add(customerUser);
        db.SaveChanges();
        var customer = new CustomerProfile { UserId = customerUser.Id };
        db.Set<CustomerProfile>().Add(customer);
        db.SaveChanges();

        // The nurse must be a real Identity user so the same phone can authenticate for the history test.
        var nurseUser = await userManager.GetUserByPhoneNumber(nursePhone);
        if (nurseUser is null)
        {
            await userManager.CreateUser(new User { UserName = $"nurse_{Guid.NewGuid():N}", PhoneNumber = nursePhone, Gender = "female", Name = "زهرا", FamilyName = "احمدی" });
            nurseUser = await userManager.GetUserByPhoneNumber(nursePhone);
        }
        var nurse = new NurseProfile { UserId = nurseUser!.Id };
        nurse.MarkVerified();
        nurse.SetAcceptingBookings(true);
        db.Set<NurseProfile>().Add(nurse);
        db.SaveChanges();

        db.Set<NurseBankAccount>().Add(new NurseBankAccount
        {
            NurseId = nurse.Id, BankName = "ملی", AccountHolderName = "زهرا", Iban = $"IR{Random.Shared.Next(100000, 999999)}0000000000000000",
            IbanHash = $"hash-{nurse.Id}", IsPrimary = true, IsVerified = true, MatchedNationalId = true,
            AccountHolderFromBank = "زهرا", OwnershipVendorRef = "mock"
        });

        var province = new Province { NameFa = "ت", NameEn = "T", SortOrder = 1, IsActive = true };
        db.Set<Province>().Add(province);
        db.SaveChanges();
        var city = new City { ProvinceId = province.Id, NameFa = "ت", NameEn = "T", SortOrder = 1, IsActive = true };
        db.Set<City>().Add(city);
        var category = new ServiceCategory { NameFa = "س", NameEn = "E", SortOrder = 1, IsActive = true };
        db.Set<ServiceCategory>().Add(category);
        db.SaveChanges();

        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 = customerUser.PhoneNumber, IsPrimary = true
        };
        db.Set<CustomerAddress>().Add(address);
        db.SaveChanges();

        var variant = new NurseServiceVariant
        {
            NurseId = nurse.Id, ServiceCategoryId = category.Id, 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 = customer.Id, NurseId = nurse.Id, PatientId = patient.Id, VariantId = variant.Id,
            CustomerAddressId = address.Id, RequiredCaregiverGender = CaregiverGender.Any,
            RequestedDate = new DateOnly(2020, 1, 1), RequestedTimeStart = new TimeOnly(9, 0), RequestedTimeEnd = new TimeOnly(13, 0),
            CustomerNotes = "n", 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 completedAt = new DateTime(2020, 1, 2, 0, 0, 0, DateTimeKind.Utc);
        var booking = new BookingEntity
        {
            BookingRequestId = request.Id, CustomerId = customer.Id, NurseId = nurse.Id, PatientId = patient.Id,
            VariantId = variant.Id, CustomerAddressId = address.Id, VariantSnapshotJson = "{}", AddressSnapshotJson = "{}",
            GrossPriceIrr = 10_000_000, BalinyaarCommissionIrr = 1_500_000, PlatformFeeRate = 0.15m,
            NursePayoutAmount = 8_500_000, SessionCount = 1,
            ScheduledDate = new DateOnly(2020, 1, 1), ScheduledTimeStart = new TimeOnly(9, 0), ScheduledTimeEnd = new TimeOnly(13, 0)
        };
        booking.TransitionTo(BookingStatus.Confirmed, completedAt);
        booking.TransitionTo(BookingStatus.InProgress, completedAt);
        booking.TransitionTo(BookingStatus.Completed, completedAt);
        booking.SetDisputeWindow(new DateTime(2020, 1, 5, 0, 0, 0, DateTimeKind.Utc)); // long past
        db.Set<BookingEntity>().Add(booking);
        db.SaveChanges();

        var legs = LedgerPosting.CardCapture(booking.Id, nurse.Id, 10_000_000, 1_500_000, 8_500_000, 0, completedAt);
        db.Set<LedgerEntry>().AddRange(legs);
        db.SaveChanges();
    }
}
