using AMREZ.EOP.Abstractions.Applications.Tenancy; using AMREZ.EOP.Domain.Shared.Tenancy; using AMREZ.EOP.Infrastructures.Data; using AMREZ.EOP.Infrastructures.Options; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; namespace AMREZ.EOP.Infrastructures.Tenancy; public sealed class TenantDbContextFactory : ITenantDbContextFactory { private readonly IOptions _opts; private readonly SearchPathInterceptor _searchPath; private readonly TenantRlsInterceptor _rls; public TenantDbContextFactory(IOptions opts, SearchPathInterceptor searchPath, TenantRlsInterceptor rls) { _opts = opts; _searchPath = searchPath; _rls = rls; } public TContext Create(ITenantContext t) where TContext : class { if (typeof(TContext) != typeof(AppDbContext)) throw new NotSupportedException($"Unsupported context: {typeof(TContext).Name}"); var builder = new DbContextOptionsBuilder(); // ✅ การ์ด: ถ้า connection string ของ tenant ไม่ใช่รูปแบบ key=value ให้ fallback ไป DefaultConnection (platform) var raw = (t.ConnectionString ?? string.Empty).Trim(); string conn = IsLikelyValidConnectionString(raw) ? raw : (_opts.Value.DefaultConnection ?? string.Empty).Trim(); if (!IsLikelyValidConnectionString(conn)) throw new InvalidOperationException( $"Invalid connection string for tenant '{t.TenantKey}'. Provide a valid per-tenant or DefaultConnection."); builder.UseNpgsql(conn); // RLS per-tenant (ถ้า platform ควรกำหนดให้ Interceptor ทำงานในโหมด platform/disable ตามที่คุณนิยาม) _rls.Configure(t.Id); builder.AddInterceptors(_rls); // ใช้ schema ต่อ tenant เมื่อเปิดโหมดนี้ if (_opts.Value.UseSchemaPerTenant) { var schema = string.IsNullOrWhiteSpace(t.Schema) ? t.Id : t.Schema!.Trim(); _searchPath.Configure(schema, enabled: true); // CREATE IF NOT EXISTS + SET LOCAL search_path builder.AddInterceptors(_searchPath); } return (new AppDbContext(builder.Options) as TContext)!; } private static bool IsLikelyValidConnectionString(string s) => !string.IsNullOrWhiteSpace(s) && s.Contains('='); }