using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
using WebClientTpos.Server.Infrastructure;

namespace WebClientTpos.Server.Controllers;

/// <summary>
/// EN: Staff controller — proxies to MerchantService for staff/roles CRUD and BookingService for schedules.
/// VI: Controller nhân viên — proxy đến MerchantService cho CRUD nhân viên/vai trò và BookingService cho lịch.
/// </summary>
[ApiController]
[Route("api/bff")]
public class StaffController : ControllerBase
{
    private readonly HttpClient _merchant;
    private readonly HttpClient _booking;
    private readonly HttpClient _iam;
    private readonly HttpClient _fnb;

    public StaffController(IHttpClientFactory httpClientFactory)
    {
        _merchant = httpClientFactory.CreateClient("MerchantService");
        _booking = httpClientFactory.CreateClient("BookingService");
        _iam = httpClientFactory.CreateClient("IamService");
        _fnb = httpClientFactory.CreateClient("FnbEngine");
    }

    /// <summary>
    /// EN: Get all staff members for the current merchant.
    /// VI: Lấy tất cả nhân viên của merchant hiện tại.
    /// </summary>
    [HttpGet("staff")]
    public Task<IActionResult> GetStaff() =>
        _merchant.GetAsync("/api/v1/merchants/me/staff").ProxyAsync();

    /// <summary>
    /// EN: Create a staff member.
    /// VI: Tạo nhân viên mới.
    /// </summary>
    [HttpPost("staff")]
    public Task<IActionResult> CreateStaff([FromBody] JsonElement body) =>
        _merchant.PostAsJsonAsync("/api/v1/merchants/me/staff", body).ProxyAsync();

    /// <summary>
    /// EN: Create IAM account + invite staff in one call.
    /// VI: Tạo tài khoản IAM + mời nhân viên trong một lần gọi.
    /// </summary>
    [HttpPost("staff/invite-with-account")]
    public async Task<IActionResult> InviteStaffWithAccount([FromBody] JsonElement body)
    {
        // Step 1: Create IAM account
        var iamPayload = new
        {
            email = body.TryGetProperty("email", out var e) ? e.GetString() : "",
            password = body.TryGetProperty("password", out var pw) ? pw.GetString() : "",
            firstName = body.TryGetProperty("firstName", out var fn) ? fn.GetString() : "",
            lastName = body.TryGetProperty("lastName", out var ln) ? ln.GetString() : ""
        };
        var iamResponse = await _iam.PostAsJsonAsync("/api/v1/auth/register", iamPayload);
        if (!iamResponse.IsSuccessStatusCode)
        {
            var errBody = await iamResponse.Content.ReadAsStringAsync();
            // EN: Extract user-friendly validation messages from ProblemDetails JSON
            // VI: Trích thông báo lỗi dễ hiểu từ ProblemDetails JSON
            var friendlyMessage = "Lỗi tạo tài khoản IAM.";
            try
            {
                using var doc = JsonDocument.Parse(errBody);
                if (doc.RootElement.TryGetProperty("errors", out var errors))
                {
                    var msgs = new List<string>();
                    foreach (var prop in errors.EnumerateObject())
                        foreach (var val in prop.Value.EnumerateArray())
                            msgs.Add(val.GetString() ?? prop.Name);
                    if (msgs.Count > 0) friendlyMessage = string.Join(". ", msgs) + ".";
                }
                else if (doc.RootElement.TryGetProperty("detail", out var detail))
                    friendlyMessage = detail.GetString() ?? friendlyMessage;
            }
            catch { /* use default message */ }
            return StatusCode((int)iamResponse.StatusCode,
                new { success = false, message = friendlyMessage });
        }

        // Step 2: Extract userId from IAM response
        Guid? iamUserId = null;
        try
        {
            var iamJson = await iamResponse.Content.ReadAsStringAsync();
            using var iamDoc = JsonDocument.Parse(iamJson);
            // IAM returns ApiResponse<RegisterUserCommandResult> with data.userId
            if (iamDoc.RootElement.TryGetProperty("data", out var data) &&
                data.TryGetProperty("userId", out var uid))
                iamUserId = Guid.TryParse(uid.GetString(), out var parsed) ? parsed : null;
        }
        catch { /* fallback to invite flow */ }

        if (iamUserId.HasValue)
        {
            // Step 3a: Create staff directly as Active (owner created the account)
            var createPayload = new
            {
                userId = iamUserId.Value,
                email = iamPayload.email,
                role = body.TryGetProperty("role", out var r1) ? r1.GetString() : "Cashier",
                shopId = body.TryGetProperty("shopId", out var s1) ? s1.GetString() : null as string,
                firstName = iamPayload.firstName,
                lastName = iamPayload.lastName,
                employeeCode = body.TryGetProperty("employeeCode", out var ec1) ? ec1.GetString() : null as string,
                phone = body.TryGetProperty("phone", out var ph1) ? ph1.GetString() : null as string,
                address = body.TryGetProperty("address", out var ad1) ? ad1.GetString() : null as string
            };
            return await _merchant.PostAsJsonAsync("/api/v1/merchants/me/staff/create-active", createPayload).ProxyAsync();
        }
        else
        {
            // Step 3b: Fallback to invite flow if userId not extracted
            var invitePayload = new
            {
                email = iamPayload.email,
                role = body.TryGetProperty("role", out var r2) ? r2.GetString() : "Cashier",
                shopId = body.TryGetProperty("shopId", out var s2) ? s2.GetString() : null as string,
                firstName = iamPayload.firstName,
                lastName = iamPayload.lastName,
                employeeCode = body.TryGetProperty("employeeCode", out var ec2) ? ec2.GetString() : null as string,
                phone = body.TryGetProperty("phone", out var ph2) ? ph2.GetString() : null as string,
                address = body.TryGetProperty("address", out var ad2) ? ad2.GetString() : null as string
            };
            return await _merchant.PostAsJsonAsync("/api/v1/merchants/me/staff/invite", invitePayload).ProxyAsync();
        }
    }

    /// <summary>
    /// EN: Reset a staff member's password via IAM service (admin action).
    /// VI: Đặt lại mật khẩu nhân viên qua IAM service (hành động admin).
    /// </summary>
    [HttpPost("staff/reset-password")]
    public async Task<IActionResult> ResetStaffPassword([FromBody] JsonElement body)
    {
        var userId = body.TryGetProperty("userId", out var uid) ? uid.GetString() : null;
        var newPassword = body.TryGetProperty("newPassword", out var np) ? np.GetString() : null;
        if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(newPassword))
            return BadRequest(new { success = false, message = "userId và newPassword là bắt buộc." });

        var payload = new { newPassword };
        return await _iam.PostAsJsonAsync($"/api/v1/users/{userId}/reset-password", payload).ProxyAsync();
    }

    /// <summary>
    /// EN: Update a staff member.
    /// VI: Cập nhật nhân viên.
    /// </summary>
    [HttpPut("staff/{staffId:guid}")]
    public Task<IActionResult> UpdateStaff(Guid staffId, [FromBody] JsonElement body) =>
        _merchant.PutAsJsonAsync($"/api/v1/merchants/me/staff/{staffId}", body).ProxyAsync();

    /// <summary>
    /// EN: Terminate (soft-delete) a staff member.
    /// VI: Chấm dứt (xóa mềm) nhân viên.
    /// </summary>
    [HttpDelete("staff/{staffId:guid}")]
    public Task<IActionResult> DeleteStaff(Guid staffId) =>
        _merchant.DeleteAsync($"/api/v1/merchants/me/staff/{staffId}").ProxyAsync();

    /// <summary>
    /// EN: Get current logged-in user's staff profile (role, shop assignment).
    /// VI: Lấy thông tin nhân viên của user đang đăng nhập (vai trò, phân công shop).
    /// </summary>
    [HttpGet("staff/me")]
    public async Task<IActionResult> GetMyStaffProfile()
    {
        // EN: Get all staff for this merchant — AuthForwardingHandler auto-forwards the Bearer token.
        //     Fallback: if merchant-service rejects (staff user is not owner), try staff lookup by email.
        // VI: Lấy tất cả nhân viên của merchant — AuthForwardingHandler tự động chuyển tiếp Bearer token.
        //     Fallback: nếu merchant-service từ chối (staff user không phải owner), thử tìm theo email.
        var authHeader = Request.Headers["Authorization"].FirstOrDefault();
        var (currentUserId, currentEmail) = ExtractUserClaimsFromJwt(
            Request.Cookies.TryGetValue("bff_session", out var cookie) ? $"Bearer {cookie}" : authHeader);

        var staffResp = await _merchant.GetAsync("/api/v1/merchants/me/staff");
        if (!staffResp.IsSuccessStatusCode)
            return StatusCode((int)staffResp.StatusCode, await staffResp.Content.ReadAsStringAsync());

        var staffJson = await staffResp.Content.ReadAsStringAsync();
        using var doc = JsonDocument.Parse(staffJson);

        // EN: Extract items from response envelope — merchant-service may return plain array or wrapped object.
        // VI: Trích items từ response envelope — merchant-service có thể trả về plain array hoặc wrapped object.
        JsonElement items;
        if (doc.RootElement.ValueKind == JsonValueKind.Array)
            items = doc.RootElement;
        else if (doc.RootElement.TryGetProperty("data", out var dataEl) && dataEl.TryGetProperty("items", out var di))
            items = di;
        else if (doc.RootElement.TryGetProperty("items", out var ri))
            items = ri;
        else
            return NotFound(new { success = false, message = "No staff data found" });

        // EN: Use userId + email already extracted from session cookie (line 177-178).
        //     Previous bug: ExtractUserIdFromJwt(authHeader) used Authorization header which is null for cookie-based BFF auth.
        // VI: Dùng userId + email đã extract từ session cookie (dòng 177-178).
        //     Bug trước: ExtractUserIdFromJwt(authHeader) dùng Authorization header mà null khi dùng cookie-based BFF auth.
        var userId = currentUserId;

        // EN: Find matching staff member by userId, then by email fallback
        // VI: Tìm nhân viên khớp theo userId, fallback theo email
        JsonElement? matchedStaff = null;
        if (items.ValueKind == JsonValueKind.Array && !string.IsNullOrEmpty(userId))
        {
            foreach (var staff in items.EnumerateArray())
            {
                if (staff.TryGetProperty("userId", out var uidProp) &&
                    string.Equals(uidProp.GetString(), userId, StringComparison.OrdinalIgnoreCase))
                {
                    matchedStaff = staff;
                    break;
                }
            }
        }

        // EN: Fallback: match by email if userId match failed
        // VI: Fallback: match theo email nếu userId không khớp
        if (matchedStaff == null && !string.IsNullOrEmpty(currentEmail) && items.ValueKind == JsonValueKind.Array)
        {
            foreach (var staff in items.EnumerateArray())
            {
                if (staff.TryGetProperty("email", out var emailProp) &&
                    string.Equals(emailProp.GetString(), currentEmail, StringComparison.OrdinalIgnoreCase))
                {
                    matchedStaff = staff;
                    break;
                }
            }
        }

        if (matchedStaff == null)
            return NotFound(new { success = false, message = "Staff profile not found for current user" });

        var s = matchedStaff.Value;

        // EN: Build profile response with shop assignment info
        // VI: Xây dựng response profile với thông tin phân công shop
        string? shopRole = null;
        Guid? shopId = null;
        string? shopName = null;

        if (s.TryGetProperty("shopAssignments", out var assignments) && assignments.ValueKind == JsonValueKind.Array)
        {
            foreach (var a in assignments.EnumerateArray())
            {
                if (a.TryGetProperty("shopId", out var sid))
                    shopId = Guid.TryParse(sid.GetString(), out var parsed) ? parsed : null;
                if (a.TryGetProperty("shopRole", out var sr))
                    shopRole = sr.GetString();
                break; // EN: Use first assignment / VI: Dùng phân công đầu tiên
            }
        }

        var profile = new
        {
            staffId = s.TryGetProperty("id", out var idP) ? idP.GetString() : null,
            userId = s.TryGetProperty("userId", out var uidP) ? uidP.GetString() : null,
            email = s.TryGetProperty("email", out var emP) ? emP.GetString() : null,
            firstName = s.TryGetProperty("firstName", out var fnP) ? fnP.GetString() : null,
            lastName = s.TryGetProperty("lastName", out var lnP) ? lnP.GetString() : null,
            role = s.TryGetProperty("role", out var rP) ? rP.GetString() : shopRole,
            shopName,
            shopId,
            shopRole,
            status = s.TryGetProperty("status", out var stP) ? stP.GetString() : null,
            employeeCode = s.TryGetProperty("employeeCode", out var ecP) ? ecP.GetString() : null,
            phone = s.TryGetProperty("phone", out var phP) ? phP.GetString() : null
        };

        // EN: Try to get shop name if shopId is available
        // VI: Thử lấy tên shop nếu có shopId
        if (shopId.HasValue)
        {
            try
            {
                var shopResp = await _merchant.GetAsync($"/api/v1/shops/{shopId}");
                if (shopResp.IsSuccessStatusCode)
                {
                    var shopJson = await shopResp.Content.ReadAsStringAsync();
                    using var shopDoc = JsonDocument.Parse(shopJson);
                    if (shopDoc.RootElement.TryGetProperty("data", out var shopData) && shopData.TryGetProperty("name", out var sName))
                        shopName = sName.GetString();
                    else if (shopDoc.RootElement.TryGetProperty("name", out var sNameDirect))
                        shopName = sNameDirect.GetString();
                }
            }
            catch { /* non-fatal */ }

            if (shopName != null)
            {
                return Ok(new { success = true, data = new
                {
                    profile.staffId, profile.userId, profile.email, profile.firstName, profile.lastName,
                    profile.role, shopName, profile.shopId, profile.shopRole, profile.status, profile.employeeCode, profile.phone
                }});
            }
        }

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

    /// <summary>
    /// EN: Get attendance records for current staff — proxies to merchant-service.
    /// VI: Lấy bản ghi chấm công của nhân viên hiện tại — proxy đến merchant-service.
    /// </summary>
    [HttpGet("staff/me/attendance")]
    public async Task<IActionResult> GetMyAttendance([FromQuery] int month = 0, [FromQuery] int year = 0)
    {
        var staffProfile = await ResolveStaffProfileAsync();
        if (staffProfile == null)
            return NotFound(new { success = false, message = "Staff profile not found" });

        var staffId = staffProfile.Value.staffId;
        var qs = $"?month={month}&year={year}";
        return await _merchant.GetAsync($"/api/v1/attendance/staff/{staffId}{qs}").ProxyAsync();
    }

    /// <summary>
    /// EN: Get leave requests for current staff — proxies to merchant-service.
    ///     Uses staffId from query param or resolves from JWT.
    /// VI: Lấy yêu cầu nghỉ phép của nhân viên hiện tại — proxy đến merchant-service.
    ///     Dùng staffId từ query param hoặc resolve từ JWT.
    /// </summary>
    [HttpGet("staff/me/leave-requests")]
    public async Task<IActionResult> GetMyLeaveRequests([FromQuery] Guid? staffId = null)
    {
        var resolvedStaffId = staffId;
        if (!resolvedStaffId.HasValue || resolvedStaffId == Guid.Empty)
        {
            var staffProfile = await ResolveStaffProfileAsync();
            if (staffProfile == null)
                return Ok(new { success = true, data = new { items = new List<object>() } });
            resolvedStaffId = staffProfile.Value.staffId;
        }

        return await _merchant.GetAsync($"/api/v1/leave-requests/staff/{resolvedStaffId}").ProxyAsync();
    }

    /// <summary>
    /// EN: Create a leave request — proxies to merchant-service.
    ///     Accepts staffId/shopId from body or resolves from JWT.
    /// VI: Tạo yêu cầu nghỉ phép — proxy đến merchant-service.
    ///     Nhận staffId/shopId từ body hoặc resolve từ JWT.
    /// </summary>
    [HttpPost("staff/me/leave-requests")]
    public async Task<IActionResult> CreateLeaveRequest([FromBody] JsonElement body)
    {
        Guid? staffId = body.TryGetProperty("staffId", out var si) && Guid.TryParse(si.GetString(), out var sp) ? sp : null;
        Guid? shopId = body.TryGetProperty("shopId", out var shi) && Guid.TryParse(shi.GetString(), out var shp) ? shp : null;

        if (!staffId.HasValue || staffId == Guid.Empty || !shopId.HasValue || shopId == Guid.Empty)
        {
            var staffProfile = await ResolveStaffProfileAsync();
            if (staffProfile != null)
            {
                staffId ??= staffProfile.Value.staffId;
                shopId ??= staffProfile.Value.shopId;
            }
            else
                return NotFound(new { success = false, message = "Staff profile not found. Please provide staffId and shopId." });
        }

        var payload = new
        {
            staffId,
            shopId,
            leaveType = body.TryGetProperty("leaveType", out var lt) ? lt.GetString() : "Annual",
            startDate = body.TryGetProperty("startDate", out var sd) ? sd.GetString() : null,
            endDate = body.TryGetProperty("endDate", out var ed) ? ed.GetString() : null,
            reason = body.TryGetProperty("reason", out var rn) ? rn.GetString() : null
        };
        return await _merchant.PostAsJsonAsync("/api/v1/leave-requests", payload).ProxyAsync();
    }

    /// <summary>
    /// EN: Get leave requests for a shop (admin) — proxies to merchant-service.
    /// VI: Lấy yêu cầu nghỉ phép theo shop (admin) — proxy đến merchant-service.
    /// </summary>
    [HttpGet("shops/{shopId:guid}/leave-requests")]
    public Task<IActionResult> GetShopLeaveRequests(Guid shopId) =>
        _merchant.GetAsync($"/api/v1/leave-requests/shop/{shopId}").ProxyAsync();

    /// <summary>
    /// EN: Approve a leave request (admin) — extracts userId from JWT and proxies to merchant-service.
    /// VI: Duyệt yêu cầu nghỉ phép (admin) — trích userId từ JWT và proxy đến merchant-service.
    /// </summary>
    [HttpPost("leave-requests/{id:guid}/approve")]
    public Task<IActionResult> ApproveLeaveRequest(Guid id)
    {
        var userId = ExtractUserIdFromJwt(Request.Headers.Authorization.FirstOrDefault());
        var approvedBy = !string.IsNullOrEmpty(userId) && Guid.TryParse(userId, out var uid) ? uid : Guid.NewGuid();
        return _merchant.PostAsJsonAsync($"/api/v1/leave-requests/{id}/approve", new { approvedBy }).ProxyAsync();
    }

    /// <summary>
    /// EN: Reject a leave request (admin) — extracts userId from JWT and proxies to merchant-service.
    /// VI: Từ chối yêu cầu nghỉ phép (admin) — trích userId từ JWT và proxy đến merchant-service.
    /// </summary>
    [HttpPost("leave-requests/{id:guid}/reject")]
    public Task<IActionResult> RejectLeaveRequest(Guid id, [FromBody] JsonElement body)
    {
        var userId = ExtractUserIdFromJwt(Request.Headers.Authorization.FirstOrDefault());
        var rejectedBy = !string.IsNullOrEmpty(userId) && Guid.TryParse(userId, out var uid) ? uid : Guid.NewGuid();
        var reason = body.TryGetProperty("reason", out var r) ? r.GetString() : null;
        return _merchant.PostAsJsonAsync($"/api/v1/leave-requests/{id}/reject", new { rejectedBy, reason }).ProxyAsync();
    }

    /// <summary>
    /// EN: Get notifications for current staff. Returns empty list until notification service is implemented.
    /// VI: Lấy thông báo của nhân viên hiện tại. Trả về danh sách trống cho đến khi notification service được triển khai.
    /// </summary>
    [HttpGet("staff/me/notifications")]
    public IActionResult GetMyNotifications()
    {
        // EN: No notification service yet — return empty list (no fake data in production).
        // VI: Chưa có notification service — trả về danh sách trống (không fake data trong production).
        return Ok(new { success = true, data = new { items = Array.Empty<object>() } });
    }

    /// <summary>
    /// EN: Check in attendance — proxies to merchant-service.
    /// VI: Chấm công vào — proxy đến merchant-service.
    /// </summary>
    [HttpPost("staff/me/attendance/check-in")]
    public async Task<IActionResult> CheckIn()
    {
        var staffProfile = await ResolveStaffProfileAsync();
        if (staffProfile == null)
            return NotFound(new { success = false, message = "Staff profile not found" });

        var payload = new { staffId = staffProfile.Value.staffId, shopId = staffProfile.Value.shopId };
        return await _merchant.PostAsJsonAsync("/api/v1/attendance/check-in", payload).ProxyAsync();
    }

    /// <summary>
    /// EN: Check out attendance — proxies to merchant-service.
    /// VI: Chấm công ra — proxy đến merchant-service.
    /// </summary>
    [HttpPost("staff/me/attendance/check-out")]
    public async Task<IActionResult> CheckOut()
    {
        var staffProfile = await ResolveStaffProfileAsync();
        if (staffProfile == null)
            return NotFound(new { success = false, message = "Staff profile not found" });

        var payload = new { staffId = staffProfile.Value.staffId };
        return await _merchant.PostAsJsonAsync("/api/v1/attendance/check-out", payload).ProxyAsync();
    }

    /// <summary>
    /// EN: Get attendance records for a shop (admin) — proxies to merchant-service.
    /// VI: Lấy bản ghi chấm công theo shop (admin) — proxy đến merchant-service.
    /// </summary>
    [HttpGet("shops/{shopId:guid}/attendance")]
    public Task<IActionResult> GetShopAttendance(Guid shopId, [FromQuery] int month = 0, [FromQuery] int year = 0)
    {
        var qs = $"?month={month}&year={year}";
        return _merchant.GetAsync($"/api/v1/attendance/shop/{shopId}{qs}").ProxyAsync();
    }

    /// <summary>
    /// EN: Get all available staff roles.
    /// VI: Lấy tất cả vai trò nhân viên hiện có.
    /// </summary>
    [HttpGet("staff/roles")]
    public Task<IActionResult> GetStaffRoles() =>
        _merchant.GetAsync("/api/v1/staff/roles").ProxyAsync();

    /// <summary>
    /// EN: Get staff schedules — proxies to BookingService.
    /// VI: Lấy lịch làm việc nhân viên — proxy đến BookingService.
    /// </summary>
    [HttpGet("staff/schedules")]
    public Task<IActionResult> GetStaffSchedules([FromQuery] Guid? shopId = null)
    {
        var qs = shopId.HasValue ? $"?shopId={shopId}" : "";
        return _booking.GetAsync($"/api/v1/schedules{qs}").ProxyAsync();
    }

    /// <summary>
    /// EN: Create a staff schedule — proxies to BookingService.
    /// VI: Tạo lịch làm việc nhân viên — proxy đến BookingService.
    /// </summary>
    [HttpPost("staff/schedules")]
    public Task<IActionResult> CreateSchedule([FromBody] JsonElement body) =>
        _booking.PostAsJsonAsync("/api/v1/schedules", body).ProxyAsync();

    /// <summary>
    /// EN: Update a staff schedule — proxies to BookingService.
    /// VI: Cập nhật lịch làm việc nhân viên — proxy đến BookingService.
    /// </summary>
    [HttpPut("staff/schedules/{scheduleId:guid}")]
    public Task<IActionResult> UpdateSchedule(Guid scheduleId, [FromBody] JsonElement body) =>
        _booking.PutAsJsonAsync($"/api/v1/schedules/{scheduleId}", body).ProxyAsync();

    /// <summary>
    /// EN: Delete a staff schedule — proxies to BookingService.
    /// VI: Xóa lịch làm việc nhân viên — proxy đến BookingService.
    /// </summary>
    [HttpDelete("staff/schedules/{scheduleId:guid}")]
    public Task<IActionResult> DeleteSchedule(Guid scheduleId) =>
        _booking.DeleteAsync($"/api/v1/schedules/{scheduleId}").ProxyAsync();

    // ═══ KITCHEN DISPLAY ═══

    /// <summary>
    /// EN: Get kitchen tickets for the current staff's shop — proxies to FnbEngine.
    /// VI: Lấy phiếu bếp cho shop của nhân viên — proxy đến FnbEngine.
    /// </summary>
    [HttpGet("kitchen/tickets")]
    public async Task<IActionResult> GetKitchenTickets([FromQuery] string? status = null)
    {
        var staffProfile = await ResolveStaffProfileAsync();
        if (staffProfile == null)
            return NotFound(new { success = false, message = "Staff profile not found" });

        var qs = $"?shopId={staffProfile.Value.shopId}";
        if (!string.IsNullOrEmpty(status)) qs += $"&status={status}";
        return await _fnb.GetAsync($"/api/v1/kitchen/tickets{qs}").ProxyAsync();
    }

    /// <summary>
    /// EN: Update kitchen ticket status — proxies to FnbEngine.
    /// VI: Cập nhật trạng thái phiếu bếp — proxy đến FnbEngine.
    /// </summary>
    [HttpPatch("kitchen/tickets/{ticketId:guid}/status")]
    public Task<IActionResult> UpdateKitchenTicketStatus(Guid ticketId, [FromBody] JsonElement body) =>
        _fnb.PatchAsync($"/api/v1/kitchen/tickets/{ticketId}/status",
            JsonContent.Create(body)).ProxyAsync();

    /// <summary>
    /// EN: Create kitchen ticket — proxies to FnbEngine.
    /// VI: Tạo phiếu bếp — proxy đến FnbEngine.
    /// </summary>
    [HttpPost("kitchen/tickets")]
    public Task<IActionResult> CreateKitchenTicket([FromBody] JsonElement body) =>
        _fnb.PostAsJsonAsync("/api/v1/kitchen/tickets", body).ProxyAsync();

    // ═══ HELPER METHODS ═══

    /// <summary>
    /// EN: Resolve current user's staff profile (staffId, shopId) by decoding JWT sub and matching in staff list.
    /// VI: Giải mã staff profile (staffId, shopId) từ JWT sub và match trong danh sách nhân viên.
    /// </summary>
    private async Task<(Guid staffId, Guid shopId)?> ResolveStaffProfileAsync()
    {
        var authHeader = Request.Headers["Authorization"].FirstOrDefault();
        var (userId, email) = ExtractUserClaimsFromJwt(authHeader);
        if (userId == null && email == null) return null;

        var staffResp = await _merchant.GetAsync("/api/v1/merchants/me/staff");
        if (!staffResp.IsSuccessStatusCode) return null;

        var staffJson = await staffResp.Content.ReadAsStringAsync();
        using var doc = JsonDocument.Parse(staffJson);

        var items = doc.RootElement.ValueKind == JsonValueKind.Array
            ? doc.RootElement
            : doc.RootElement.TryGetProperty("data", out var d) && d.TryGetProperty("items", out var di) ? di
            : doc.RootElement.TryGetProperty("items", out var ri) ? ri
            : default;

        if (items.ValueKind != JsonValueKind.Array) return null;

        // EN: Try matching by userId first, then fallback to email
        // VI: Thử match theo userId trước, sau đó fallback theo email
        foreach (var staff in items.EnumerateArray())
        {
            if (staff.TryGetProperty("userId", out var uidProp) &&
                string.Equals(uidProp.GetString(), userId, StringComparison.OrdinalIgnoreCase))
            {
                return ExtractStaffProfile(staff);
            }
        }

        // EN: Fallback: match by email in staff list
        // VI: Fallback: match theo email trong danh sách nhân viên
        if (email != null)
        {
            foreach (var staff in items.EnumerateArray())
            {
                if (staff.TryGetProperty("email", out var emailProp) &&
                    string.Equals(emailProp.GetString(), email, StringComparison.OrdinalIgnoreCase))
                {
                    return ExtractStaffProfile(staff);
                }
            }
        }

        // EN: Final fallback: use staff lookup endpoint by email (handles cross-env userId mismatch)
        // VI: Fallback cuối: dùng staff lookup theo email (xử lý userId khác nhau giữa các môi trường)
        if (email != null)
        {
            try
            {
                var lookupResp = await _merchant.GetAsync($"/api/v1/staff/lookup?email={Uri.EscapeDataString(email)}");
                if (lookupResp.IsSuccessStatusCode)
                {
                    var lookupJson = await lookupResp.Content.ReadAsStringAsync();
                    using var lookupDoc = JsonDocument.Parse(lookupJson);
                    if (lookupDoc.RootElement.TryGetProperty("data", out var data))
                    {
                        var sId = data.TryGetProperty("staffId", out var si) && Guid.TryParse(si.GetString(), out var sp) ? sp : Guid.Empty;
                        var shId = data.TryGetProperty("shopId", out var shi) && Guid.TryParse(shi.GetString(), out var shp) ? shp : Guid.Empty;
                        if (sId != Guid.Empty) return (sId, shId);
                    }
                }
            }
            catch { /* non-fatal */ }
        }

        return null;
    }

    private static (Guid staffId, Guid shopId)? ExtractStaffProfile(JsonElement staff)
    {
        var staffId = staff.TryGetProperty("id", out var sid) && Guid.TryParse(sid.GetString(), out var s) ? s : Guid.Empty;
        Guid shopId = Guid.Empty;
        if (staff.TryGetProperty("shopAssignments", out var sa) && sa.ValueKind == JsonValueKind.Array)
        {
            foreach (var a in sa.EnumerateArray())
            {
                if (a.TryGetProperty("shopId", out var shopProp) && Guid.TryParse(shopProp.GetString(), out var sp))
                { shopId = sp; break; }
            }
        }
        if (staffId != Guid.Empty) return (staffId, shopId);
        return null;
    }

    /// <summary>
    /// EN: Extract userId (sub) and email from JWT token.
    /// VI: Trích userId (sub) và email từ JWT token.
    /// </summary>
    private static (string? userId, string? email) ExtractUserClaimsFromJwt(string? authHeader)
    {
        if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
            return (null, null);
        try
        {
            var token = authHeader["Bearer ".Length..];
            var parts = token.Split('.');
            if (parts.Length < 2) return (null, null);
            var payload = parts[1].Replace('-', '+').Replace('_', '/');
            switch (payload.Length % 4) { case 2: payload += "=="; break; case 3: payload += "="; break; }
            using var jwtDoc = JsonDocument.Parse(Convert.FromBase64String(payload));
            var sub = jwtDoc.RootElement.TryGetProperty("sub", out var s) ? s.GetString() : null;
            var email = jwtDoc.RootElement.TryGetProperty("email", out var e) ? e.GetString() : null;
            return (sub, email);
        }
        catch { return (null, null); }
    }

    private static string? ExtractUserIdFromJwt(string? authHeader) => ExtractUserClaimsFromJwt(authHeader).userId;
}
