using System.Text.RegularExpressions; using AMREZ.EOP.Abstractions.Applications.Tenancy; using AMREZ.EOP.Contracts.DTOs.Authentications.Login; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; namespace AMREZ.EOP.Infrastructures.Tenancy; public sealed class DefaultTenantResolver : ITenantResolver { private readonly TenantMap _map; private readonly ILogger? _log; // แพลตฟอร์มสลัก (ปรับตามระบบจริงได้) — ใช้ "public" เป็นค่าเริ่ม private const string PlatformSlug = "public"; public DefaultTenantResolver( TenantMap map, ILogger? log = null ) { _map = map; _log = log; } public ITenantContext? Resolve(HttpContext http, object? hint = null) { var cid = http.TraceIdentifier; var path = http.Request.Path.Value ?? string.Empty; if (hint is string hs && string.Equals(hs, "@platform", StringComparison.OrdinalIgnoreCase)) { if (_map.TryGetBySlug(PlatformSlug, out var platform)) { _log?.LogInformation("Resolved by hint=@platform -> {Slug} cid={Cid}", PlatformSlug, cid); return platform; } _log?.LogWarning("Platform slug '{Slug}' not found in TenantMap cid={Cid}", PlatformSlug, cid); return null; } if (path.StartsWith("/api/Tenancy", StringComparison.OrdinalIgnoreCase)) { if (_map.TryGetBySlug(PlatformSlug, out var platform)) { // เก็บ target tenant แยกไว้ให้ repo/usecase ใช้ var headerTenant = http.Request.Headers["X-Tenant"].ToString(); if (!string.IsNullOrWhiteSpace(headerTenant)) http.Items["TargetTenantKey"] = headerTenant.Trim().ToLowerInvariant(); _log?.LogInformation("Resolved CONTROL-PLANE -> platform:{Slug} (path={Path}) cid={Cid}", PlatformSlug, path, cid); return platform; } _log?.LogWarning("CONTROL-PLANE path but platform slug '{Slug}' missing cid={Cid}", PlatformSlug, cid); return null; } var header = http.Request.Headers["X-Tenant"].ToString(); if (!string.IsNullOrWhiteSpace(header) && _map.TryGetBySlug(header, out var byHeader)) { _log?.LogInformation("Resolved by header: {Tenant} cid={Cid}", header, cid); return byHeader; } var host = http.Request.Host.Host; if (_map.TryGetByDomain(host, out var byDomain)) { _log?.LogInformation("Resolved by domain: {Host} -> {TenantId} cid={Cid}", host, byDomain.Id, cid); return byDomain; } var sub = GetSubdomain(host); if (sub != null && _map.TryGetBySlug(sub, out var bySub)) { _log?.LogInformation("Resolved by subdomain: {Sub} cid={Cid}", sub, cid); return bySub; } if (hint is LoginRequest body && !string.IsNullOrWhiteSpace(body.Tenant) && _map.TryGetBySlug(body.Tenant!, out var byBody)) { _log?.LogInformation("Resolved by body: {Tenant} cid={Cid}", body.Tenant, cid); return byBody; } _log?.LogWarning("Resolve FAILED host={Host} header={Header} cid={Cid}", host, string.IsNullOrWhiteSpace(header) ? "" : header, cid); return null; } private static string? GetSubdomain(string host) { if (Regex.IsMatch(host, @"^\d{1,3}(\.\d{1,3}){3}$")) return null; // IP var parts = host.Split('.', StringSplitOptions.RemoveEmptyEntries); return parts.Length >= 3 ? parts[0] : null; } }