using JobsMedical.Web.Models;
using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace JobsMedical.Web.Data;

public class AppDbContext : DbContext, IDataProtectionKeyContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

    /// <summary>DataProtection key ring — persisted so antiforgery/cookies survive deploys & replicas.</summary>
    public DbSet<DataProtectionKey> DataProtectionKeys => Set<DataProtectionKey>();

    public DbSet<City> Cities => Set<City>();
    public DbSet<District> Districts => Set<District>();
    public DbSet<Role> Roles => Set<Role>();
    public DbSet<User> Users => Set<User>();
    public DbSet<DoctorProfile> DoctorProfiles => Set<DoctorProfile>();
    public DbSet<Facility> Facilities => Set<Facility>();
    public DbSet<Shift> Shifts => Set<Shift>();
    public DbSet<JobOpening> JobOpenings => Set<JobOpening>();
    public DbSet<TalentListing> TalentListings => Set<TalentListing>();
    public DbSet<ContactMethod> ContactMethods => Set<ContactMethod>();
    public DbSet<Application> Applications => Set<Application>();
    public DbSet<RawListing> RawListings => Set<RawListing>();
    public DbSet<Visitor> Visitors => Set<Visitor>();
    public DbSet<UserPreferences> UserPreferences => Set<UserPreferences>();
    public DbSet<InterestEvent> InterestEvents => Set<InterestEvent>();
    public DbSet<AppSetting> AppSettings => Set<AppSetting>();
    public DbSet<WebPushSubscription> WebPushSubscriptions => Set<WebPushSubscription>();
    public DbSet<Notification> Notifications => Set<Notification>();
    public DbSet<Report> Reports => Set<Report>();
    public DbSet<FacilityDocument> FacilityDocuments => Set<FacilityDocument>();
    public DbSet<JobAlert> JobAlerts => Set<JobAlert>();
    public DbSet<IngestionRun> IngestionRuns => Set<IngestionRun>();
    public DbSet<Review> Reviews => Set<Review>();

    protected override void OnModelCreating(ModelBuilder b)
    {
        base.OnModelCreating(b);

        // Phone is the unique identity.
        b.Entity<User>().HasIndex(u => u.Phone).IsUnique();

        // One-to-one User <-> DoctorProfile.
        b.Entity<DoctorProfile>()
            .HasOne(d => d.User)
            .WithOne(u => u.DoctorProfile)
            .HasForeignKey<DoctorProfile>(d => d.UserId)
            .OnDelete(DeleteBehavior.Cascade);

        // A doctor can apply to a shift only once.
        b.Entity<Application>()
            .HasIndex(a => new { a.ShiftId, a.DoctorId })
            .IsUnique();

        b.Entity<Application>()
            .HasOne(a => a.Doctor)
            .WithMany(u => u.Applications)
            .HasForeignKey(a => a.DoctorId)
            .OnDelete(DeleteBehavior.Cascade);

        b.Entity<Application>()
            .HasOne(a => a.Shift)
            .WithMany(s => s.Applications)
            .HasForeignKey(a => a.ShiftId)
            .OnDelete(DeleteBehavior.Cascade);

        // Facility owner is optional (Phase 2 self-serve); don't cascade-delete users.
        b.Entity<Facility>()
            .HasOne(f => f.OwnerUser)
            .WithMany()
            .HasForeignKey(f => f.OwnerUserId)
            .OnDelete(DeleteBehavior.SetNull);

        // Common query indexes: filtering shifts by city/date/status happens constantly.
        b.Entity<Shift>().HasIndex(s => new { s.Date, s.Status });
        b.Entity<Shift>().HasIndex(s => s.FacilityId);
        b.Entity<Shift>().HasIndex(s => s.RoleId);
        b.Entity<Facility>().HasIndex(f => f.CityId);
        b.Entity<Facility>().HasIndex(f => f.DistrictId);

        b.Entity<District>()
            .HasOne(d => d.City).WithMany()
            .HasForeignKey(d => d.CityId).OnDelete(DeleteBehavior.Cascade);
        b.Entity<Facility>()
            .HasOne(f => f.District).WithMany(d => d.Facilities)
            .HasForeignKey(f => f.DistrictId).OnDelete(DeleteBehavior.SetNull);

        // Verification documents belong to a facility; remove them with it.
        b.Entity<FacilityDocument>()
            .HasOne(d => d.Facility).WithMany(f => f.Documents)
            .HasForeignKey(d => d.FacilityId).OnDelete(DeleteBehavior.Cascade);

        // Job alerts belong to a user; remove with the user. Don't cascade from Role.
        b.Entity<JobAlert>()
            .HasOne(a => a.User).WithMany()
            .HasForeignKey(a => a.UserId).OnDelete(DeleteBehavior.Cascade);
        b.Entity<JobAlert>()
            .HasOne(a => a.Role).WithMany()
            .HasForeignKey(a => a.RoleId).OnDelete(DeleteBehavior.SetNull);
        b.Entity<JobAlert>().HasIndex(a => a.IsActive);

        // Reviews: one per (facility, user); remove with either.
        b.Entity<Review>().HasIndex(r => new { r.FacilityId, r.UserId }).IsUnique();
        b.Entity<Review>().HasOne(r => r.Facility).WithMany()
            .HasForeignKey(r => r.FacilityId).OnDelete(DeleteBehavior.Cascade);
        b.Entity<Review>().HasOne(r => r.User).WithMany()
            .HasForeignKey(r => r.UserId).OnDelete(DeleteBehavior.Cascade);

        // Don't delete shifts/profiles just because a Role is removed.
        b.Entity<Shift>()
            .HasOne(s => s.Role).WithMany(r => r.Shifts)
            .HasForeignKey(s => s.RoleId).OnDelete(DeleteBehavior.Restrict);

        // Visitor identity + behavioral tracking.
        b.Entity<Visitor>()
            .HasOne(v => v.User).WithMany()
            .HasForeignKey(v => v.UserId).OnDelete(DeleteBehavior.SetNull);

        b.Entity<UserPreferences>()
            .HasOne(p => p.Visitor).WithOne(v => v.Preferences)
            .HasForeignKey<UserPreferences>(p => p.VisitorId).OnDelete(DeleteBehavior.Cascade);

        b.Entity<InterestEvent>()
            .HasOne(e => e.Visitor).WithMany(v => v.Events)
            .HasForeignKey(e => e.VisitorId).OnDelete(DeleteBehavior.Cascade);
        b.Entity<InterestEvent>()
            .HasOne(e => e.Shift).WithMany()
            .HasForeignKey(e => e.ShiftId).OnDelete(DeleteBehavior.Cascade);
        b.Entity<InterestEvent>()
            .HasOne(e => e.JobOpening).WithMany()
            .HasForeignKey(e => e.JobOpeningId).OnDelete(DeleteBehavior.Cascade);
        // Reading "this visitor's recent events" is the hot path for recommendations.
        b.Entity<InterestEvent>().HasIndex(e => new { e.VisitorId, e.CreatedAt });

        // Job openings: same query patterns as shifts.
        b.Entity<JobOpening>()
            .HasOne(j => j.Role).WithMany()
            .HasForeignKey(j => j.RoleId).OnDelete(DeleteBehavior.Restrict);
        b.Entity<JobOpening>()
            .HasOne(j => j.Facility).WithMany()
            .HasForeignKey(j => j.FacilityId).OnDelete(DeleteBehavior.Cascade);
        b.Entity<JobOpening>().HasIndex(j => j.Status);
        b.Entity<JobOpening>().HasIndex(j => j.FacilityId);

        // Talent listings («آماده به کار») — no facility; keep role/city but don't cascade from them.
        b.Entity<TalentListing>()
            .HasOne(t => t.Role).WithMany()
            .HasForeignKey(t => t.RoleId).OnDelete(DeleteBehavior.Restrict);
        b.Entity<TalentListing>()
            .HasOne(t => t.City).WithMany()
            .HasForeignKey(t => t.CityId).OnDelete(DeleteBehavior.Restrict);
        b.Entity<TalentListing>()
            .HasOne(t => t.District).WithMany()
            .HasForeignKey(t => t.DistrictId).OnDelete(DeleteBehavior.SetNull);
        b.Entity<TalentListing>().HasIndex(t => t.Status);
        b.Entity<TalentListing>().HasIndex(t => new { t.CityId, t.RoleId });
        // A ContactMethod belongs to exactly one of talent / shift / job (all optional FKs).
        b.Entity<ContactMethod>()
            .HasOne(c => c.TalentListing).WithMany(t => t.Contacts)
            .HasForeignKey(c => c.TalentListingId).OnDelete(DeleteBehavior.Cascade);
        b.Entity<ContactMethod>()
            .HasOne(c => c.Shift).WithMany(s => s.Contacts)
            .HasForeignKey(c => c.ShiftId).OnDelete(DeleteBehavior.Cascade);
        b.Entity<ContactMethod>()
            .HasOne(c => c.JobOpening).WithMany(j => j.Contacts)
            .HasForeignKey(c => c.JobOpeningId).OnDelete(DeleteBehavior.Cascade);

        b.Entity<WebPushSubscription>().HasIndex(s => s.Endpoint).IsUnique();

        b.Entity<Notification>()
            .HasOne(n => n.User).WithMany()
            .HasForeignKey(n => n.UserId).OnDelete(DeleteBehavior.Cascade);
        b.Entity<Notification>().HasIndex(n => new { n.UserId, n.IsRead, n.CreatedAt });
        b.Entity<Report>().HasIndex(r => r.Status);

        // Dedupe ingested listings by content hash.
        b.Entity<RawListing>().HasIndex(r => r.ContentHash);
        b.Entity<RawListing>().HasIndex(r => r.Status);
        // A RawListing only LINKS to the post it produced — it must outlive that post (it's the
        // dedupe cache). So deleting a Shift/Talent NULLs the back-reference rather than orphaning a
        // dangling FK or blocking the delete. LinkedTalentId previously had no FK at all (orphan risk).
        b.Entity<RawListing>()
            .HasOne(r => r.LinkedShift).WithMany()
            .HasForeignKey(r => r.LinkedShiftId).OnDelete(DeleteBehavior.SetNull);
        b.Entity<RawListing>()
            .HasOne(r => r.LinkedTalent).WithMany()
            .HasForeignKey(r => r.LinkedTalentId).OnDelete(DeleteBehavior.SetNull);
    }
}
