// EN: BFF Auth controller — handles login/logout/session for WASM client.
//     Token exchange uses server-side credentials; WASM never sees the raw access token.
//     Access token is stored in an httpOnly SameSite=Strict cookie, not in localStorage.
// VI: BFF Auth controller — xử lý login/logout/session cho WASM client.
//     Trao đổi token dùng thông tin xác thực server-side; WASM không bao giờ thấy token thô.
//     Access token được lưu trong httpOnly SameSite=Strict cookie, không phải localStorage.

using System.Text.Json;
using Microsoft.AspNetCore.Mvc;

namespace WebClientTpos.Server.Controllers;

/// <summary>
/// EN: BFF (Backend-for-Frontend) authentication controller.
///     - Receives email/password from WASM, exchanges with IS4 using server-side client credentials.
///     - Stores access token in an httpOnly cookie (never sent to WASM JS).
///     - WASM reads session info (email, role, expiry) without ever seeing the raw token.
/// VI: BFF Auth controller.
///     - Nhận email/password từ WASM, trao đổi với IS4 dùng client credentials server-side.
///     - Lưu access token trong httpOnly cookie (không gửi sang WASM JS).
///     - WASM đọc thông tin session (email, role, expiry) mà không thấy token thô.
/// </summary>
[ApiController]
[Route("api/bff/auth")]
public class BffAuthController : ControllerBase
{
    // EN: httpOnly cookie name — must match AuthForwardingHandler.SessionCookieName.
    // VI: Tên cookie httpOnly — phải khớp với AuthForwardingHandler.SessionCookieName.
    internal const string SessionCookieName = "bff_session";
    private const string CookiePath = "/";

    private readonly IHttpClientFactory _clientFactory;
    private readonly IConfiguration _config;
    private readonly ILogger<BffAuthController> _logger;

    public BffAuthController(
        IHttpClientFactory clientFactory,
        IConfiguration config,
        ILogger<BffAuthController> logger)
    {
        _clientFactory = clientFactory;
        _config = config;
        _logger = logger;
    }

    /// <summary>
    /// EN: Login — accepts email/password from WASM, proxies token exchange to IS4 using
    ///     server-side client credentials. Sets an httpOnly session cookie on success.
    ///     Returns session info (email, role, expiresAt) — never the raw token.
    /// VI: Đăng nhập — nhận email/password từ WASM, proxy trao đổi token đến IS4 dùng
    ///     client credentials server-side. Đặt httpOnly session cookie khi thành công.
    ///     Trả về thông tin session (email, role, expiresAt) — không bao giờ trả token thô.
    /// </summary>
    [HttpPost("login")]
    public async Task<IActionResult> Login([FromBody] BffLoginRequest request, CancellationToken ct)
    {
        if (string.IsNullOrWhiteSpace(request.Email) || string.IsNullOrWhiteSpace(request.Password))
            return BadRequest(new { success = false, message = "Email và mật khẩu là bắt buộc. / Email and password are required." });

        var clientId = _config["IdentityServer:ClientId"] ?? "bff-client";
        var clientSecret = _config["IdentityServer:ClientSecret"] ?? "";
        var tokenEndpoint = _config["IdentityServer:TokenEndpoint"] ?? "/connect/token";
        var scope = _config["IdentityServer:Scope"] ?? "openid profile email api";

        var iamClient = _clientFactory.CreateClient("IamService");

        // EN: Build password grant form — client credentials stay on the server, never in WASM.
        // VI: Tạo form password grant — client credentials ở lại server, không bao giờ trong WASM.
        var form = new FormUrlEncodedContent(
        [
            new KeyValuePair<string, string>("grant_type", "password"),
            new KeyValuePair<string, string>("client_id", clientId),
            new KeyValuePair<string, string>("client_secret", clientSecret),
            new KeyValuePair<string, string>("username", request.Email),
            new KeyValuePair<string, string>("password", request.Password),
            new KeyValuePair<string, string>("scope", scope),
        ]);

        HttpResponseMessage is4Response;
        try
        {
            is4Response = await iamClient.PostAsync(tokenEndpoint, form, ct);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "EN: Failed to reach IS4 token endpoint {Endpoint}.", tokenEndpoint);
            return StatusCode(503, new { success = false, message = "Không thể kết nối dịch vụ xác thực. / Auth service unavailable." });
        }

        if (!is4Response.IsSuccessStatusCode)
        {
            var errorBody = await is4Response.Content.ReadAsStringAsync(ct);
            _logger.LogWarning("EN: IS4 login failed for {Email}. Status: {Status}.", request.Email, is4Response.StatusCode);
            if (errorBody.Contains("invalid_grant"))
                return Unauthorized(new { success = false, message = "Email hoặc mật khẩu không đúng. / Invalid email or password." });
            return Unauthorized(new { success = false, message = "Đăng nhập thất bại. / Login failed." });
        }

        var json = await is4Response.Content.ReadAsStringAsync(ct);
        string? accessToken;
        int expiresIn;
        try
        {
            using var doc = JsonDocument.Parse(json);
            accessToken = doc.RootElement.GetProperty("access_token").GetString();
            expiresIn = doc.RootElement.TryGetProperty("expires_in", out var expEl) ? expEl.GetInt32() : 3600;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "EN: Failed to parse IS4 token response.");
            return StatusCode(500, new { success = false, message = "Token không hợp lệ. / Invalid token response." });
        }

        if (string.IsNullOrEmpty(accessToken))
            return StatusCode(500, new { success = false, message = "Token rỗng từ IS4. / Empty token from IS4." });

        // EN: Store token in httpOnly SameSite=Strict cookie — never accessible via WASM JavaScript.
        // VI: Lưu token trong httpOnly SameSite=Strict cookie — không truy cập được từ JavaScript WASM.
        var isLocalhost = HttpContext.Request.Host.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase)
                       || HttpContext.Request.Host.Host.Equals("127.0.0.1", StringComparison.OrdinalIgnoreCase);

        Response.Cookies.Append(SessionCookieName, accessToken, new CookieOptions
        {
            HttpOnly = true,
            Secure = !isLocalhost,
            SameSite = SameSiteMode.Strict,
            Path = CookiePath,
            Expires = DateTimeOffset.UtcNow.AddSeconds(expiresIn),
        });

        // EN: Return session info (no raw token) — WASM uses this to show user name/role.
        // VI: Trả về thông tin session (không có token thô) — WASM dùng để hiển thị tên/vai trò.
        var sessionInfo = ExtractSessionFromJwt(accessToken, expiresIn);
        _logger.LogInformation("EN: BFF login success for {Email} role={Role}.", sessionInfo?.Email, sessionInfo?.Role);

        // EN: Fire-and-forget audit log — don't block login response
        // VI: Ghi audit log fire-and-forget — không chặn login response
        _ = Task.Run(async () =>
        {
            try
            {
                var auditClient = _clientFactory.CreateClient("IamService");
                auditClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", $"Bearer {accessToken}");
                await auditClient.PostAsJsonAsync("/api/v1/audit/logs", new
                {
                    eventTypeId = 1, // Login
                    resourceType = "User",
                    actorId = sessionInfo?.UserId,
                    actorEmail = sessionInfo?.Email,
                    action = "Login via BFF",
                    details = $"Role: {sessionInfo?.Role}",
                    success = true
                });
            }
            catch (Exception ex)
            {
                _logger.LogWarning(ex, "Failed to create audit log for login");
            }
        });

        return Ok(new { success = true, data = sessionInfo });
    }

    /// <summary>
    /// EN: Session — returns current session info decoded from the httpOnly cookie.
    ///     Returns 401 if no valid session cookie exists or if token is expired.
    /// VI: Session — trả về thông tin session được giải mã từ httpOnly cookie.
    ///     Trả về 401 nếu không có cookie session hợp lệ hoặc token đã hết hạn.
    /// </summary>
    [HttpGet("session")]
    public IActionResult GetSession()
    {
        if (!Request.Cookies.TryGetValue(SessionCookieName, out var token) || string.IsNullOrEmpty(token))
            return Unauthorized(new { success = false, message = "No active session." });

        var sessionInfo = ExtractSessionFromJwt(token, 0);
        if (sessionInfo == null)
        {
            // EN: Session cookie exists but token is expired — clear it.
            // VI: Cookie tồn tại nhưng token đã hết hạn — xóa cookie.
            Response.Cookies.Delete(SessionCookieName, new CookieOptions { Path = CookiePath });
            return Unauthorized(new { success = false, message = "Session hết hạn. / Session expired." });
        }

        return Ok(new { success = true, data = sessionInfo });
    }

    /// <summary>
    /// EN: Logout — deletes the httpOnly session cookie.
    /// VI: Đăng xuất — xóa httpOnly session cookie.
    /// </summary>
    [HttpPost("logout")]
    public IActionResult Logout()
    {
        Response.Cookies.Delete(SessionCookieName, new CookieOptions { Path = CookiePath });
        _logger.LogInformation("EN: BFF logout — session cookie cleared.");
        return Ok(new { success = true });
    }

    // ═══════════════════════════════════════════════════════════════
    // EN: Helpers
    // VI: Hàm hỗ trợ
    // ═══════════════════════════════════════════════════════════════

    /// <summary>
    /// EN: Decode JWT claims to get session info without exposing the raw token.
    ///     Returns null if token is expired or malformed.
    /// VI: Giải mã JWT claims để lấy thông tin session mà không lộ token thô.
    ///     Trả về null nếu token hết hạn hoặc sai định dạng.
    /// </summary>
    private static BffSessionInfo? ExtractSessionFromJwt(string token, int expiresInSeconds)
    {
        try
        {
            var parts = token.Split('.');
            if (parts.Length < 2) return null;

            var payload = parts[1].Replace('-', '+').Replace('_', '/');
            switch (payload.Length % 4) { case 2: payload += "=="; break; case 3: payload += "="; break; }
            using var doc = JsonDocument.Parse(Convert.FromBase64String(payload));
            var root = doc.RootElement;

            var email = root.TryGetProperty("email", out var eProp) ? eProp.GetString() : null;
            var sub = root.TryGetProperty("sub", out var sProp) ? sProp.GetString() : null;

            // EN: Extract role — may be a string or array of strings.
            // VI: Lấy role — có thể là string hoặc mảng string.
            string? role = null;
            if (root.TryGetProperty("role", out var roleProp))
            {
                role = roleProp.ValueKind == JsonValueKind.Array
                    ? roleProp.EnumerateArray().FirstOrDefault().GetString()
                    : roleProp.GetString();
            }

            // EN: Determine expiry — prefer explicit expiresInSeconds, fallback to JWT 'exp' claim.
            // VI: Xác định thời hạn — ưu tiên expiresInSeconds, fallback sang claim 'exp' của JWT.
            DateTime? expiresAt = null;
            if (expiresInSeconds > 0)
            {
                expiresAt = DateTime.UtcNow.AddSeconds(expiresInSeconds);
            }
            else if (root.TryGetProperty("exp", out var expProp))
            {
                expiresAt = DateTimeOffset.FromUnixTimeSeconds(expProp.GetInt64()).UtcDateTime;
            }

            // EN: Reject expired sessions.
            // VI: Từ chối session đã hết hạn.
            if (expiresAt.HasValue && expiresAt.Value <= DateTime.UtcNow)
                return null;

            return new BffSessionInfo(sub, email, role ?? "owner", expiresAt);
        }
        catch
        {
            return null;
        }
    }
}

/// <summary>
/// EN: Login request DTO — only email/password, no client credentials.
/// VI: DTO request đăng nhập — chỉ email/password, không có client credentials.
/// </summary>
public record BffLoginRequest(string Email, string Password);

/// <summary>
/// EN: Session info returned to WASM — does NOT include the raw access token.
/// VI: Thông tin session trả về cho WASM — KHÔNG bao gồm access token thô.
/// </summary>
public record BffSessionInfo(string? UserId, string? Email, string Role, DateTime? ExpiresAt);
