using foodmarket.Application.Common.Tenancy;
using foodmarket.Domain.Catalog;
using foodmarket.Domain.Common;
using foodmarket.Domain.Inventory;
using foodmarket.Domain.Purchases;
using foodmarket.Domain.Sales;
using foodmarket.Infrastructure.Identity;
using foodmarket.Domain.Organizations;
using foodmarket.Infrastructure.Persistence.Configurations;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace foodmarket.Infrastructure.Persistence;

public class AppDbContext : IdentityDbContext<User, Role, Guid>
{
    private readonly ITenantContext _tenant;

    public AppDbContext(DbContextOptions<AppDbContext> options, ITenantContext tenant)
        : base(options)
    {
        _tenant = tenant;
    }

    public DbSet<Organization> Organizations => Set<Organization>();

    public DbSet<Country> Countries => Set<Country>();
    public DbSet<Currency> Currencies => Set<Currency>();
    public DbSet<UnitOfMeasure> UnitsOfMeasure => Set<UnitOfMeasure>();
    public DbSet<OrgUnitOfMeasure> OrgUnitsOfMeasure => Set<OrgUnitOfMeasure>();
    public DbSet<Counterparty> Counterparties => Set<Counterparty>();
    public DbSet<Store> Stores => Set<Store>();
    public DbSet<RetailPoint> RetailPoints => Set<RetailPoint>();
    public DbSet<ProductGroup> ProductGroups => Set<ProductGroup>();
    public DbSet<PriceType> PriceTypes => Set<PriceType>();
    public DbSet<Product> Products => Set<Product>();
    public DbSet<ProductPrice> ProductPrices => Set<ProductPrice>();
    public DbSet<ProductBarcode> ProductBarcodes => Set<ProductBarcode>();
    public DbSet<ProductImage> ProductImages => Set<ProductImage>();

    public DbSet<Stock> Stocks => Set<Stock>();
    public DbSet<StockMovement> StockMovements => Set<StockMovement>();

    public DbSet<Supply> Supplies => Set<Supply>();
    public DbSet<SupplyLine> SupplyLines => Set<SupplyLine>();

    public DbSet<RetailSale> RetailSales => Set<RetailSale>();
    public DbSet<RetailSaleLine> RetailSaleLines => Set<RetailSaleLine>();

    public DbSet<EmployeeRole> EmployeeRoles => Set<EmployeeRole>();
    public DbSet<Employee> Employees => Set<Employee>();
    public DbSet<EmployeeRetailPointAssignment> EmployeeRetailPointAssignments => Set<EmployeeRetailPointAssignment>();
    public DbSet<SuperAdminAuditLog> SuperAdminAuditLogs => Set<SuperAdminAuditLog>();
    public DbSet<SystemSettings> SystemSettings => Set<SystemSettings>();
    public DbSet<foodmarket.Domain.Platform.PlatformSettings> PlatformSettings => Set<foodmarket.Domain.Platform.PlatformSettings>();

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

        builder.HasDefaultSchema("public");

        // OpenIddict EF Core tables
        builder.UseOpenIddict();

        // Identity table renaming for clarity
        builder.Entity<User>(b =>
        {
            b.ToTable("users");
            b.Property(u => u.FullName).HasMaxLength(200);
        });
        builder.Entity<Role>(b => b.ToTable("roles"));

        builder.Entity<Organization>(b =>
        {
            b.ToTable("organizations");
            b.Property(o => o.Name).HasMaxLength(200).IsRequired();
            b.Property(o => o.CountryCode).HasMaxLength(2).IsRequired();
            b.Property(o => o.Bin).HasMaxLength(20);
            b.Property(o => o.MoySkladToken).HasMaxLength(200);
            b.HasOne(o => o.DefaultCurrency).WithMany().HasForeignKey(o => o.DefaultCurrencyId).OnDelete(DeleteBehavior.Restrict);
            b.HasIndex(o => o.Name);
            b.HasIndex(o => o.IsArchived);
        });

        builder.Entity<SystemSettings>(b =>
        {
            b.ToTable("system_settings");
        });

        builder.Entity<foodmarket.Domain.Platform.PlatformSettings>(b =>
        {
            b.ToTable("platform_settings");
            b.Property(x => x.SmtpHost).HasMaxLength(200);
            b.Property(x => x.SmtpUsername).HasMaxLength(200);
            b.Property(x => x.FromEmail).HasMaxLength(200);
            b.Property(x => x.FromName).HasMaxLength(200);
        });

        builder.Entity<SuperAdminAuditLog>(b =>
        {
            b.ToTable("super_admin_audit_log");
            b.Property(x => x.ActionType).HasMaxLength(50).IsRequired();
            b.Property(x => x.EntityType).HasMaxLength(100);
            b.Property(x => x.Description).HasMaxLength(500);
            b.Property(x => x.Reason).HasMaxLength(1000);
            b.Property(x => x.ChangesJson).HasColumnType("jsonb");
            b.Property(x => x.IpAddress).HasMaxLength(45);
            b.HasIndex(x => x.CreatedAt);
            b.HasIndex(x => new { x.SuperAdminUserId, x.CreatedAt });
            b.HasIndex(x => new { x.OrganizationId, x.CreatedAt });
        });

        builder.ConfigureCatalog();
        builder.ConfigureInventory();
        builder.ConfigurePurchases();
        builder.ConfigureSales();
        builder.ConfigureOrganizationsHr();

        // Apply multi-tenant query filter to every entity that implements ITenantEntity.
        // IOptionalTenantEntity (системные справочники с nullable OrganizationId) —
        // через отдельный фильтр, который пропускает запись с NULL для всех.
        foreach (var entityType in builder.Model.GetEntityTypes())
        {
            if (typeof(ITenantEntity).IsAssignableFrom(entityType.ClrType))
            {
                var method = typeof(AppDbContext)
                    .GetMethod(nameof(ApplyTenantFilter),
                        System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!
                    .MakeGenericMethod(entityType.ClrType);
                method.Invoke(this, new object[] { builder });
            }
            else if (typeof(IOptionalTenantEntity).IsAssignableFrom(entityType.ClrType))
            {
                var method = typeof(AppDbContext)
                    .GetMethod(nameof(ApplyOptionalTenantFilter),
                        System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!
                    .MakeGenericMethod(entityType.ClrType);
                method.Invoke(this, new object[] { builder });
            }
        }
    }

    private void ApplyTenantFilter<T>(ModelBuilder builder) where T : class, ITenantEntity
    {
        // SuperAdmin обходит фильтр ТОЛЬКО когда не в режиме «открыть как…».
        // В override-режиме (X-Org-Override header активен) он работает в
        // контексте конкретной орги — фильтр обязан применяться, иначе
        // возвращаются записи всех орг и нарушается tenant isolation.
        builder.Entity<T>().HasQueryFilter(e =>
            (_tenant.IsSuperAdmin && !_tenant.IsTenantOverride)
            || e.OrganizationId == _tenant.OrganizationId);
    }

    private void ApplyOptionalTenantFilter<T>(ModelBuilder builder) where T : class, IOptionalTenantEntity
    {
        // Системные записи (OrganizationId == null) видны ВСЕМ tenant'ам как
        // эталонные. Tenant'овские (свои OrganizationId) — обычная изоляция.
        // SuperAdmin без override видит всё.
        builder.Entity<T>().HasQueryFilter(e =>
            (_tenant.IsSuperAdmin && !_tenant.IsTenantOverride)
            || e.OrganizationId == null
            || e.OrganizationId == _tenant.OrganizationId);
    }

    public override int SaveChanges()
    {
        StampTenant();
        StampTimestamps();
        return base.SaveChanges();
    }

    public override Task<int> SaveChangesAsync(CancellationToken ct = default)
    {
        StampTenant();
        StampTimestamps();
        return base.SaveChangesAsync(ct);
    }

    private void StampTenant()
    {
        foreach (var entry in ChangeTracker.Entries())
        {
            if (entry.State != EntityState.Added) continue;
            if (entry.Entity is ITenantEntity tenant && tenant.OrganizationId == Guid.Empty)
            {
                if (_tenant.OrganizationId.HasValue)
                    tenant.OrganizationId = _tenant.OrganizationId.Value;
            }
            else if (entry.Entity is IOptionalTenantEntity opt && opt.OrganizationId is null)
            {
                // Если SuperAdmin создаёт запись «как пользователь» (override
                // активен), стампим выбранную орг. Если SuperAdmin без override
                // (системная консоль) — оставляем null (системная запись).
                // Tenant-юзер всегда стампит свой orgId.
                if (_tenant.IsSuperAdmin && !_tenant.IsTenantOverride)
                {
                    // null — системная запись; оставляем
                }
                else if (_tenant.OrganizationId.HasValue)
                {
                    opt.OrganizationId = _tenant.OrganizationId.Value;
                }
            }
        }
    }

    private void StampTimestamps()
    {
        foreach (var entry in ChangeTracker.Entries<Entity>())
        {
            if (entry.State == EntityState.Added)
                entry.Entity.CreatedAt = DateTime.UtcNow;
            else if (entry.State == EntityState.Modified)
                entry.Entity.UpdatedAt = DateTime.UtcNow;
        }
    }
}
