#nullable enable
using Baya.Application.Features.Messaging.Commands.AutoCreateCoordinationTicket;
using Baya.Domain.Entities.Booking;
using Baya.Domain.Entities.Messaging;
using Baya.Domain.Entities.Reviews;
using Baya.Domain.Entities.SupportAlerts;
using Baya.Domain.Entities.Verification;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using OpsNotification = Baya.Domain.Entities.Notifications.Notification;

namespace Baya.Infrastructure.Persistence.Services.Seeding;

internal sealed partial class DemoLifecycleSeeder
{
    // ---------------------------------------------------------------------------------------------------
    // Reviews — one per moderation state, plus the recomputed-from-source nurse aggregates.
    // ---------------------------------------------------------------------------------------------------

    private async Task EnsureReviewsAsync(
        PersonaWorld world, BookingWorld bookings, DateTime now, CancellationToken cancellationToken)
    {
        var mohammadi = world.Customers[0];
        var hosseini = world.Customers[1];
        var azizi = world.Nurses[0];
        var karimi = world.Nurses[1];
        var seededAny = false;

        // Published 5★ with tags — the public social proof on the paid-out booking.
        if (bookings.ByKey.TryGetValue("completed_paid", out var paid)
            && !await db.Set<Review>().AnyAsync(r => r.BookingId == paid.Id, cancellationToken))
        {
            var review = new Review
            {
                BookingId = paid.Id,
                CustomerProfileId = mohammadi.ProfileId,
                NurseProfileId = azizi.Profile.Id,
                Rating = 5,
                Body = "بسیار دلسوز و حرفه‌ای. سر وقت رسید و همه‌چیز را با حوصله توضیح داد."
            };
            review.Moderate(ReviewModerationStatus.Published, null, world.AdminUserId, now.AddDays(-6));
            db.Set<Review>().Add(review);
            await db.SaveChangesAsync(cancellationToken); // assigns review.Id for the tag links

            var tagIds = await db.Set<ReviewTagMaster>()
                .Where(t => t.Code == ReviewTagCodes.Punctual || t.Code == ReviewTagCodes.Professional)
                .Select(t => t.Id)
                .ToListAsync(cancellationToken);
            foreach (var tagId in tagIds)
                db.Set<ReviewTagLink>().Add(new ReviewTagLink { ReviewId = review.Id, ReviewTagMasterId = tagId });
            seededAny = true;
        }

        // Pending moderation — populates the admin moderation queue; never publicly visible.
        if (bookings.ByKey.TryGetValue("completed_in_window", out var inWindow)
            && !await db.Set<Review>().AnyAsync(r => r.BookingId == inWindow.Id, cancellationToken))
        {
            db.Set<Review>().Add(new Review
            {
                BookingId = inWindow.Id,
                CustomerProfileId = mohammadi.ProfileId,
                NurseProfileId = azizi.Profile.Id,
                Rating = 4,
                Body = "مراقبت خوب بود، فقط کمی دیر رسیدند."
            });
            seededAny = true;
        }

        // Hidden 1★ — moderated away; excluded from the public list and the aggregate, feeds the
        // low-rating support alert.
        if (bookings.ByKey.TryGetValue("completed_eligible", out var eligible)
            && !await db.Set<Review>().AnyAsync(r => r.BookingId == eligible.Id, cancellationToken))
        {
            var review = new Review
            {
                BookingId = eligible.Id,
                CustomerProfileId = hosseini.ProfileId,
                NurseProfileId = karimi.Profile.Id,
                Rating = 1,
                Body = "اصلاً راضی نبودم."
            };
            review.Moderate(ReviewModerationStatus.Hidden, "irrelevant_content", world.AdminUserId, now.AddDays(-4));
            db.Set<Review>().Add(review);
            seededAny = true;
        }

        if (seededAny)
            await db.SaveChangesAsync(cancellationToken);

        // Aggregates recomputed from source, mirroring RecomputeNurseRating's published-only rule and the
        // completed-bookings count — never free-entered numbers.
        foreach (var nurse in new[] { azizi, karimi })
        {
            var published = await db.Set<Review>()
                .Where(r => r.NurseProfileId == nurse.Profile.Id && r.ModerationStatus == ReviewModerationStatus.Published)
                .GroupBy(_ => 1)
                .Select(g => new { Count = g.Count(), Sum = g.Sum(r => r.Rating) })
                .FirstOrDefaultAsync(cancellationToken);
            nurse.Profile.SetReviewAggregates(
                published is null ? 0m : Math.Round((decimal)published.Sum / published.Count, 2, MidpointRounding.AwayFromZero),
                published?.Count ?? 0);

            var completed = await db.Set<Booking>()
                .CountAsync(b => b.NurseId == nurse.Profile.Id && b.Status == BookingStatus.Completed, cancellationToken);
            nurse.Profile.SetCompletedBookings(completed);
        }
        await db.SaveChangesAsync(cancellationToken);

        await EnsureSupportAlertsAsync(bookings, cancellationToken);
    }

    /// <summary>The staff worklist rows real usage would have raised: the low rating, the out-of-tolerance
    /// EVV check-in, the clawback receivable, and the logged emergency.</summary>
    private async Task EnsureSupportAlertsAsync(BookingWorld bookings, CancellationToken cancellationToken)
    {
        var seededAny = false;

        async Task EnsureAlertAsync(string type, string severity, string entityType, long entityId, long? bookingId, long? reviewId)
        {
            if (await db.Set<SupportAlert>().AnyAsync(a => a.Type == type && a.BookingId == bookingId, cancellationToken))
                return;
            db.Set<SupportAlert>().Add(new SupportAlert
            {
                Type = type,
                Severity = severity,
                EntityType = entityType,
                EntityId = entityId.ToString(),
                BookingId = bookingId,
                ReviewId = reviewId
            });
            seededAny = true;
        }

        if (bookings.ByKey.TryGetValue("completed_eligible", out var eligible))
        {
            var review = await db.Set<Review>()
                .Where(r => r.BookingId == eligible.Id)
                .Select(r => (long?)r.Id)
                .FirstOrDefaultAsync(cancellationToken);
            if (review is { } reviewId)
                await EnsureAlertAsync(SupportAlertType.LowRating, SupportAlertSeverity.Medium, "review", reviewId, eligible.Id, reviewId);
            await EnsureAlertAsync(SupportAlertType.EvvLocationMismatch, SupportAlertSeverity.Medium, "booking", eligible.Id, eligible.Id, null);
        }

        if (bookings.ByKey.TryGetValue("clawback", out var clawed))
            await EnsureAlertAsync(SupportAlertType.NurseClawback, SupportAlertSeverity.High, "booking", clawed.Id, clawed.Id, null);

        if (bookings.ByKey.TryGetValue("in_progress", out var inProgress))
            await EnsureAlertAsync(SupportAlertType.Emergency, SupportAlertSeverity.High, "booking", inProgress.Id, inProgress.Id, null);

        if (seededAny)
            await db.SaveChangesAsync(cancellationToken);
    }

    // ---------------------------------------------------------------------------------------------------
    // Tickets — coordination via the real command; support/emergency threads hand-built (fixed reference
    // codes double as the idempotency guards). The internal admin note proves the is_internal boundary.
    // ---------------------------------------------------------------------------------------------------

    private async Task EnsureTicketsAsync(
        PersonaWorld world, BookingWorld bookings, DateTime now, CancellationToken cancellationToken)
    {
        foreach (var (key, booking) in bookings.ByKey)
        {
            var result = await sender.Send(new AutoCreateCoordinationTicketCommand(booking.Id), cancellationToken);
            if (!result.IsSuccess)
                logger.LogDebug("Demo lifecycle: coordination ticket for '{Key}' not created.", key);
        }

        var mohammadi = world.Customers[0];
        var hosseini = world.Customers[1];
        var admin = world.AdminUserId;

        // Open support thread with an is_internal admin note the user views must never surface.
        if (!await db.Set<Ticket>().AnyAsync(t => t.ReferenceCode == DemoLifecycleDefinitions.SupportTicketOpen, cancellationToken))
        {
            var openedAt = clock.UtcNow.AddDays(-2);
            db.Set<Ticket>().Add(new Ticket
            {
                ReferenceCode = DemoLifecycleDefinitions.SupportTicketOpen,
                Subject = "فاکتور رزرو نمایش داده نمی‌شود",
                Category = TicketCategory.Support,
                OpenedById = mohammadi.UserId,
                Participants =
                [
                    new TicketParticipant { UserId = mohammadi.UserId, RoleOnTicket = TicketParticipantRole.Customer },
                    new TicketParticipant { UserId = admin, RoleOnTicket = TicketParticipantRole.Admin, AddedById = admin }
                ],
                Messages =
                [
                    new TicketMessage
                    {
                        SenderId = mohammadi.UserId,
                        Body = "سلام، فاکتور رزرو اخیرم در بخش کیف پول باز نمی‌شود.",
                        SentAt = openedAt
                    },
                    new TicketMessage
                    {
                        SenderId = admin,
                        IsInternal = true,
                        Body = "یادداشت داخلی: مشکل از سمت صدور فاکتور بود؛ صف مودیان بررسی شود.",
                        SentAt = openedAt.AddHours(1)
                    },
                    new TicketMessage
                    {
                        SenderId = admin,
                        Body = "سلام! مشکل بررسی شد و فاکتور شما اکنون قابل مشاهده است. ممنون از صبوری‌تان.",
                        SentAt = openedAt.AddHours(2)
                    },
                    new TicketMessage
                    {
                        SenderId = mohammadi.UserId,
                        Body = "ممنون، حل شد. 🌸",
                        SentAt = openedAt.AddDays(1)
                    }
                ]
            });
            await db.SaveChangesAsync(cancellationToken);
        }

        // Closed support thread — the inbox's closed filter and the admin close/reopen actions.
        if (!await db.Set<Ticket>().AnyAsync(t => t.ReferenceCode == DemoLifecycleDefinitions.SupportTicketClosed, cancellationToken))
        {
            var openedAt = clock.UtcNow.AddDays(-5);
            var ticket = new Ticket
            {
                ReferenceCode = DemoLifecycleDefinitions.SupportTicketClosed,
                Subject = "سوال درباره نحوه تغییر شماره تماس",
                Category = TicketCategory.Support,
                OpenedById = hosseini.UserId,
                Participants =
                [
                    new TicketParticipant { UserId = hosseini.UserId, RoleOnTicket = TicketParticipantRole.Customer },
                    new TicketParticipant { UserId = admin, RoleOnTicket = TicketParticipantRole.Admin, AddedById = admin }
                ],
                Messages =
                [
                    new TicketMessage
                    {
                        SenderId = hosseini.UserId,
                        Body = "چطور می‌توانم شماره موبایل حسابم را عوض کنم؟",
                        SentAt = openedAt
                    },
                    new TicketMessage
                    {
                        SenderId = admin,
                        Body = "در حال حاضر تغییر شماره از طریق پشتیبانی انجام می‌شود؛ مدارک هویتی لازم است.",
                        SentAt = openedAt.AddHours(3)
                    }
                ]
            };
            ticket.Close(admin, clock.UtcNow.AddDays(-3));
            db.Set<Ticket>().Add(ticket);
            await db.SaveChangesAsync(cancellationToken);
        }

        // Emergency aftermath thread (logged by the nurse mid-visit) — the admin queue's red row.
        if (bookings.ByKey.TryGetValue("in_progress", out var inProgress)
            && !await db.Set<Ticket>().AnyAsync(t => t.ReferenceCode == DemoLifecycleDefinitions.EmergencyTicket, cancellationToken))
        {
            var azizi = world.Nurses[0];
            db.Set<Ticket>().Add(new Ticket
            {
                ReferenceCode = DemoLifecycleDefinitions.EmergencyTicket,
                Subject = "گزارش تماس اضطراری حین ویزیت",
                Category = TicketCategory.Emergency,
                BookingId = inProgress.Id,
                OpenedById = azizi.UserId,
                Participants =
                [
                    new TicketParticipant { UserId = azizi.UserId, RoleOnTicket = TicketParticipantRole.Nurse },
                    new TicketParticipant { UserId = admin, RoleOnTicket = TicketParticipantRole.Admin, AddedById = admin }
                ],
                Messages =
                [
                    new TicketMessage
                    {
                        SenderId = azizi.UserId,
                        Body = "حین ویزیت افت فشار ناگهانی داشتیم؛ با اورژانس تماس گرفتم و وضعیت پایدار شد. خانواده در جریان است.",
                        SentAt = clock.UtcNow.AddHours(-1)
                    }
                ]
            });
            await db.SaveChangesAsync(cancellationToken);
        }
    }

    // ---------------------------------------------------------------------------------------------------
    // Patient care records — the encrypted, patient-scoped longitudinal history (handler-side encryption
    // by design: no EF converter on body_encrypted).
    // ---------------------------------------------------------------------------------------------------

    private async Task EnsureCareRecordsAsync(
        PersonaWorld world, BookingWorld bookings, DateTime now, CancellationToken cancellationToken)
    {
        var seededAny = false;

        async Task EnsureRecordAsync(long patientId, long? bookingId, long nurseProfileId, string body, string? tasksJson, DateTime recordedAt)
        {
            if (await db.Set<PatientCareRecord>().AnyAsync(
                    r => r.PatientId == patientId && r.BookingId == bookingId, cancellationToken))
                return;
            db.Set<PatientCareRecord>().Add(new PatientCareRecord
            {
                PatientId = patientId,
                BookingId = bookingId,
                NurseProfileId = nurseProfileId,
                BodyEncrypted = encryptor.Encrypt(body),
                TaskResultsJson = tasksJson,
                RecordedAt = recordedAt
            });
            seededAny = true;
        }

        var aziziProfileId = world.Nurses[0].Profile.Id;
        var karimiProfileId = world.Nurses[1].Profile.Id;
        var patientHasan = world.Customers[0].PatientIds[0];
        var patientAmirali = world.Customers[1].PatientIds[0];

        if (bookings.ByKey.TryGetValue("completed_paid", out var paid))
            await EnsureRecordAsync(
                patientHasan, paid.Id, aziziProfileId,
                "علائم حیاتی پایدار. قند خون ناشتا ۱۱۰. داروها طبق برنامه مصرف شد؛ اشتها خوب بود.",
                "[{\"label\":\"کنترل قند خون\",\"done\":true},{\"label\":\"دادن داروهای صبح\",\"done\":true}]",
                now.AddDays(-10).AddHours(4));

        if (bookings.ByKey.TryGetValue("completed_in_window", out var inWindow))
            await EnsureRecordAsync(
                patientHasan, inWindow.Id, aziziProfileId,
                "فشار خون ۱۴ روی ۹. کمی بی‌قراری عصر داشتند که با پیاده‌روی کوتاه بهتر شد. زخم پا رو به بهبود است.",
                "[{\"label\":\"پانسمان زخم پا\",\"done\":true},{\"label\":\"کنترل فشار خون\",\"done\":true},{\"label\":\"پیاده‌روی عصر\",\"done\":false}]",
                now.AddDays(-1).AddHours(4));

        if (bookings.ByKey.TryGetValue("bnpl_settled", out var bnplBooking))
            await EnsureRecordAsync(
                patientAmirali, bnplBooking.Id, karimiProfileId,
                "وضعیت عمومی نوزاد خوب. تغذیه منظم انجام شد و دمای بدن طبیعی بود.",
                "[{\"label\":\"پایش دمای بدن\",\"done\":true}]",
                now.AddDays(-6).AddHours(3));

        if (seededAny)
            await db.SaveChangesAsync(cancellationToken);
    }

    // ---------------------------------------------------------------------------------------------------
    // Mid-pipeline verification for the unverified nurse — feeds the nurse journey (B3–B6) and the admin
    // verification queue. is_verified is deliberately never touched (status != approved).
    // ---------------------------------------------------------------------------------------------------

    private async Task EnsureVerificationPipelineAsync(PersonaWorld world, DateTime now, CancellationToken cancellationToken)
    {
        var ahmadi = world.Nurses[2];
        var verification = await db.Set<NurseVerification>()
            .FirstOrDefaultAsync(v => v.NurseId == ahmadi.Profile.Id, cancellationToken);
        if (verification is null)
            return;
        if (await db.Set<VerificationStep>().AnyAsync(s => s.NurseVerificationId == verification.Id, cancellationToken))
            return;

        verification.Status = VerificationStatus.InReview;
        verification.SubmittedAt = clock.UtcNow.AddDays(-3);

        var stepTypes = await db.Set<VerificationStepType>()
            .Where(t => t.IsActive)
            .ToDictionaryAsync(t => t.Code, cancellationToken);

        VerificationStep AddStep(string code, VerificationStepStatus status, bool automated, string? responseJson)
        {
            var step = new VerificationStep
            {
                NurseVerificationId = verification.Id,
                StepTypeId = stepTypes[code].Id,
                Status = status,
                IsAutomated = automated,
                ExternalResponseJson = responseJson,
                StartedAt = clock.UtcNow.AddDays(-3),
                CompletedAt = status is VerificationStepStatus.Passed ? clock.UtcNow.AddDays(-3).AddMinutes(5) : null
            };
            db.Set<VerificationStep>().Add(step);
            return step;
        }

        AddStep(VerificationStepTypeCodes.IdentityKyc, VerificationStepStatus.Passed, automated: true,
            "{\"source\":\"demo-lifecycle-seed\",\"match\":true,\"confidence\":0.97}");
        AddStep(VerificationStepTypeCodes.ShahkarMatch, VerificationStepStatus.Passed, automated: true,
            "{\"source\":\"demo-lifecycle-seed\",\"simOwnerMatch\":true}");
        var licenseStep = AddStep(VerificationStepTypeCodes.MohCompetencyLicense, VerificationStepStatus.InReview, automated: false, null);
        AddStep(VerificationStepTypeCodes.CriminalRecord, VerificationStepStatus.Pending, automated: false, null);
        await db.SaveChangesAsync(cancellationToken); // assigns step ids for the document FK

        // The uploaded evidence file's metadata. The object-storage bytes deliberately don't exist — the
        // admin viewer's signed-URL fetch will 404, which is acceptable for a dev DB (never fake bytes).
        db.Set<VerificationDocument>().Add(new VerificationDocument
        {
            StepId = licenseStep.Id,
            ObjectStorageKey = "verification/demo/ahmadi-moh-license.jpg",
            IntegrityHash = "demo-integrity-hash",
            ContentType = "image/jpeg",
            FileSizeBytes = 245_000,
            OriginalFileName = "moh-license.jpg",
            UploadedByUserId = ahmadi.UserId
        });
        await db.SaveChangesAsync(cancellationToken);
    }

    // ---------------------------------------------------------------------------------------------------
    // Notifications — direct ops rows with backdated CreatedAt, copying the REAL handlers' type/DataJson
    // contract so the front-end deep-links resolve. (The real emitters write English copy; mirrored as-is.)
    // ---------------------------------------------------------------------------------------------------

    private async Task EnsureNotificationsAsync(
        PersonaWorld world, BookingWorld bookings, DateTime now, CancellationToken cancellationToken)
    {
        var azizi = world.Nurses[0];
        var karimi = world.Nurses[1];
        var mohammadi = world.Customers[0];

        if (await db.Set<OpsNotification>().AnyAsync(
                n => n.UserId == azizi.UserId && n.Type == "booking_request_received", cancellationToken))
            return;

        var pendingRequestId = await db.Set<BookingRequest>()
            .Where(r => r.Status == BookingRequestStatus.PendingNurseResponse && r.NurseId == azizi.Profile.Id)
            .Select(r => (long?)r.Id)
            .FirstOrDefaultAsync(cancellationToken);
        var acceptedRequest = await db.Set<BookingRequest>()
            .Where(r => r.Status == BookingRequestStatus.AcceptedAwaitingPayment && r.CustomerId == mohammadi.ProfileId)
            .Select(r => new { r.Id, r.PaymentDeadlineAt })
            .FirstOrDefaultAsync(cancellationToken);
        var expiredRequestId = await db.Set<BookingRequest>()
            .Where(r => r.Status == BookingRequestStatus.ExpiredNoResponse && r.NurseId == karimi.Profile.Id)
            .Select(r => (long?)r.Id)
            .FirstOrDefaultAsync(cancellationToken);
        var publishedReviewId = bookings.ByKey.TryGetValue("completed_paid", out var paid)
            ? await db.Set<Review>().Where(r => r.BookingId == paid.Id).Select(r => (long?)r.Id).FirstOrDefaultAsync(cancellationToken)
            : null;
        var upcomingId = bookings.ByKey.TryGetValue("upcoming", out var upcoming) ? upcoming.Id : (long?)null;

        void Add(int userId, string type, string title, string body, string dataJson, DateTime createdAt, bool read)
            => db.Set<OpsNotification>().Add(new OpsNotification
            {
                UserId = userId,
                Type = type,
                Title = title,
                Body = body,
                DataJson = dataJson,
                IsRead = read,
                ReadAt = read ? new DateTimeOffset(createdAt.AddMinutes(30), TimeSpan.Zero) : null,
                CreatedAt = new DateTimeOffset(createdAt, TimeSpan.Zero)
            });

        if (pendingRequestId is { } pendingId)
            Add(azizi.UserId, "booking_request_received", "New booking request",
                "You have a new booking request awaiting your response.",
                $"{{\"booking_request_id\":{pendingId}}}", now.AddHours(-2), read: false);

        if (upcomingId is { } bookingId)
        {
            Add(mohammadi.UserId, "booking_confirmed", "Booking confirmed",
                "Your payment was captured and your booking is confirmed.",
                $"{{\"booking_id\":{bookingId}}}", now.AddDays(-1), read: true);
            Add(azizi.UserId, "booking_confirmed_nurse", "New confirmed booking",
                "A booking has been confirmed and paid. The care instructions and schedule are now available.",
                $"{{\"booking_id\":{bookingId}}}", now.AddDays(-1), read: true);
        }

        if (acceptedRequest is not null)
            Add(mohammadi.UserId, "booking_request_accepted", "Booking request accepted",
                "The nurse accepted your request. Pay within the payment window to confirm your booking.",
                $"{{\"booking_request_id\":{acceptedRequest.Id},\"payment_deadline_at\":\"{acceptedRequest.PaymentDeadlineAt:O}\"}}",
                now.AddHours(-1), read: false);

        if (publishedReviewId is { } reviewId)
            Add(mohammadi.UserId, "review_moderated", "Your review was updated",
                "Your review is now published.",
                $"{{\"reviewId\":{reviewId},\"status\":\"published\"}}", now.AddDays(-6), read: true);

        if (expiredRequestId is { } expiredId)
            Add(karimi.UserId, "booking_request_received", "New booking request",
                "You have a new booking request awaiting your response.",
                $"{{\"booking_request_id\":{expiredId}}}", now.AddDays(-3), read: true);

        await db.SaveChangesAsync(cancellationToken);
    }
}
