using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using PSTW_CentralSystem.Areas.IT.Models;
using PSTW_CentralSystem.DBContext;
using PSTW_CentralSystem.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using PSTW_CentralSystem.Areas.IT.Printing;
using QuestPDF.Fluent;
using Org.BouncyCastle.Ocsp;

namespace PSTW_CentralSystem.Controllers.API
{
    [ApiController]
    [Route("[controller]")]
    [Authorize] // keep your policy if needed
    public class ItRequestAPI : ControllerBase
    {

        private static string Nz(string? s, string fallback = "") =>
            string.IsNullOrWhiteSpace(s) ? fallback : s;

        private readonly CentralSystemContext _db;
        private readonly UserManager<UserModel> _userManager;

        public ItRequestAPI(CentralSystemContext db, UserManager<UserModel> userManager)
        {
            _db = db;
            _userManager = userManager;
        }

        // Helper for Edit window 
        private const int EDIT_WINDOW_HOURS_DEFAULT = 24;        // <- change here anytime
        private const int EDIT_WINDOW_HOURS_MIN = 1;
        private const int EDIT_WINDOW_HOURS_MAX = 24 * 14;       // clamp for safety (2 weeks)

        // === Feature flags ===
        // Toggle this to true if later wants Pending (no approvals yet) to be cancelable.
        private const bool ALLOW_PENDING_CANCEL = false;

        private static int ResolveEditWindowHours(HttpRequest req, int? fromDto)
        {
            // Priority: DTO -> Header -> Query -> Default
            if (fromDto.HasValue) return Math.Clamp(fromDto.Value, EDIT_WINDOW_HOURS_MIN, EDIT_WINDOW_HOURS_MAX);

            if (int.TryParse(req.Headers["X-Edit-Window-Hours"], out var h))
                return Math.Clamp(h, EDIT_WINDOW_HOURS_MIN, EDIT_WINDOW_HOURS_MAX);

            if (int.TryParse(req.Query["editWindowHours"], out var q))
                return Math.Clamp(q, EDIT_WINDOW_HOURS_MIN, EDIT_WINDOW_HOURS_MAX);

            return EDIT_WINDOW_HOURS_DEFAULT;
        }



        // Helper to get current user id (int)
        private int GetCurrentUserIdOrThrow()
        {
            var userIdStr = _userManager.GetUserId(User);
            if (string.IsNullOrEmpty(userIdStr) || !int.TryParse(userIdStr, out int currentUserId))
                throw new UnauthorizedAccessException("Invalid user");
            return currentUserId;
        }
        // --------------------------------------------------------------------
        // GET: /ItRequestAPI/users?q=&take=100
        // Returns light user list for dropdowns
        // --------------------------------------------------------------------
        [HttpGet("users")]
        public async Task<IActionResult> GetUsers([FromQuery] string? q = null, [FromQuery] int take = 200)
        {
            take = Math.Clamp(take, 1, 500);

            var users = _db.Users.AsQueryable();

            if (!string.IsNullOrWhiteSpace(q))
            {
                q = q.Trim();
                users = users.Where(u =>
                    (u.UserName ?? "").Contains(q) ||
                    (u.FullName ?? "").Contains(q) ||
                    (u.Email ?? "").Contains(q));
            }

            var data = await users
                .OrderBy(u => u.Id)
                .Take(take)
                .Select(u => new
                {
                    id = u.Id,
                    name = u.FullName ?? u.UserName,
                    userName = u.UserName,
                    email = u.Email
                })
                .ToListAsync();

            return Ok(data);
        }
        // ===== Section B: Meta =====
        [HttpGet("sectionB/meta")]
        public async Task<IActionResult> SectionBMeta([FromQuery] int statusId)
        {
            int currentUserId;
            try { currentUserId = GetCurrentUserIdOrThrow(); }
            catch (Exception ex) { return Unauthorized(new { message = ex.Message }); }

            var status = await _db.ItRequestStatus
                .Include(s => s.Request)
                .Include(s => s.Flow)
                .FirstOrDefaultAsync(s => s.StatusId == statusId);

            if (status == null)
                return NotFound(new { message = "Status not found." });

            if (status.Request == null)
                return StatusCode(409, new { message = "This status row is orphaned (missing its request). Please re-create the request or fix the foreign key." });

            var req = status.Request!;
            var sectionB = await _db.ItRequestAssetInfo.FirstOrDefaultAsync(x => x.ItRequestId == status.ItRequestId);

            bool isRequestor = req.UserId == currentUserId;
            bool isItMember = await _db.ItTeamMembers.AnyAsync(t => t.UserId == currentUserId);

            bool sectionBSent = sectionB?.SectionBSent == true;
            bool locked = sectionBSent || (sectionB?.RequestorAccepted == true && sectionB?.ItAccepted == true);

            return Ok(new
            {
                overallStatus = status.OverallStatus ?? "Pending",
                requestorName = req.StaffName,
                isRequestor,
                isItMember,
                sectionB = sectionB == null ? null : new
                {
                    saved = true,
                    assetNo = sectionB.AssetNo ?? "",
                    machineId = sectionB.MachineId ?? "",
                    ipAddress = sectionB.IpAddress ?? "",
                    wiredMac = sectionB.WiredMac ?? "",
                    wifiMac = sectionB.WifiMac ?? "",
                    dialupAcc = sectionB.DialupAcc ?? "",
                    remarks = sectionB.Remarks ?? "",
                    lastEditedBy = sectionB.LastEditedByName ?? "",
                    lastEditedAt = sectionB.LastEditedAt
                },
                sectionBSent,
                sectionBSentAt = sectionB?.SectionBSentAt,
                locked,
                requestorAccepted = sectionB?.RequestorAccepted ?? false,
                requestorAcceptedAt = sectionB?.RequestorAcceptedAt,
                itAccepted = sectionB?.ItAccepted ?? false,
                itAcceptedAt = sectionB?.ItAcceptedAt,
                itAcceptedBy = sectionB?.ItAcceptedByName
            });
        }



        [HttpGet("sectionB/pdf")]
        public async Task<IActionResult> SectionBPdf([FromQuery] int statusId)
        {
            var status = await _db.ItRequestStatus
                .Include(s => s.Request).ThenInclude(r => r!.Hardware)
                .Include(s => s.Request).ThenInclude(r => r!.Emails)
                .Include(s => s.Request).ThenInclude(r => r!.OsRequirements)
                .Include(s => s.Request).ThenInclude(r => r!.Software)
                .Include(s => s.Request).ThenInclude(r => r!.SharedPermissions)
                .Include(s => s.Flow)
                .FirstOrDefaultAsync(s => s.StatusId == statusId);

            if (status == null) return NotFound();
            if (status.Request == null)
                return StatusCode(409, new { message = "Missing Request for this status." });

            var b = await _db.ItRequestAssetInfo
                .SingleOrDefaultAsync(x => x.ItRequestId == status.ItRequestId);

            string? hardwareJustification = null;
            if (status.Request?.Hardware != null)
            {
                var firstDistinct = status.Request.Hardware
                    .Select(h => h.Justification?.Trim())
                    .Where(j => !string.IsNullOrWhiteSpace(j))
                    .Distinct(StringComparer.InvariantCultureIgnoreCase)
                    .FirstOrDefault();

                hardwareJustification = firstDistinct;
            }


            if (status is null) return NotFound();
            if (status.Request is null)
                return StatusCode(409, new { message = "Missing Request for this status." });

            var req = status.Request; // non-null after the guard


            // Build the report model
            var m = new ItRequestReportModel
            {
                ItRequestId = status.ItRequestId,
                StatusId = status.StatusId,
                RevNo = status.StatusId.ToString(),
                OverallStatus = status.OverallStatus ?? "Pending",
                SubmitDate = req.SubmitDate,                 // if nullable: req.SubmitDate ?? DateTime.MinValue

                StaffName = req.StaffName ?? "",
                CompanyName = req.CompanyName ?? "",
                DepartmentName = req.DepartmentName ?? "",
                Designation = req.Designation ?? "",
                Location = req.Location ?? "",
                EmploymentStatus = req.EmploymentStatus ?? "",
                ContractEndDate = req.ContractEndDate,
                RequiredDate = req.RequiredDate,
                PhoneExt = req.PhoneExt ?? "",

                // …keep the rest, but switch to 'req' for child lists too:
                Hardware = req.Hardware?.Select(h =>
                                string.IsNullOrWhiteSpace(h.OtherDescription)
                                    ? (h.Category ?? "")
                                    : $"{h.Category} – {h.OtherDescription}")
                .Where(s => !string.IsNullOrWhiteSpace(s)).ToList() ?? new(),

                Justification = hardwareJustification,

                Emails = req.Emails?
                .Select(e => e.ProposedAddress ?? "")
                .Where(s => !string.IsNullOrWhiteSpace(s))
                .ToList() ?? new(),

                OsRequirements = req.OsRequirements?
                        .Select(o => o.RequirementText ?? "")
                        .Where(s => !string.IsNullOrWhiteSpace(s))
                        .ToList() ?? new(),

                Software = req.Software?
                    .Select(sw => (sw.Bucket ?? "", sw.Name ?? "", sw.OtherName, sw.Notes))
                    .ToList() ?? new(),

                SharedPerms = req.SharedPermissions?
                    .Select(sp => (sp.ShareName ?? "", sp.CanRead, sp.CanWrite, sp.CanDelete, sp.CanRemove))
                    .ToList() ?? new(),

                AssetNo = b?.AssetNo ?? "",
                MachineId = b?.MachineId ?? "",
                IpAddress = b?.IpAddress ?? "",
                WiredMac = b?.WiredMac ?? "",
                WifiMac = b?.WifiMac ?? "",
                DialupAcc = b?.DialupAcc ?? "",
                Remarks = b?.Remarks ?? "",
                RequestorName = req.StaffName ?? "",
                RequestorAcceptedAt = b?.RequestorAcceptedAt,
                ItCompletedBy = b?.ItAcceptedByName ?? "",
                ItAcceptedAt = b?.ItAcceptedAt
            };


            if (status.Request?.Hardware != null)
            {
                foreach (var h in status.Request.Hardware)
                {
                    var cat = (h.Category ?? "").Trim();
                    var purpose = (h.Purpose ?? "").Trim().ToLowerInvariant();

                    // Purposes (left column)
                    if (purpose == "newrecruitment" || purpose == "new recruitment" || purpose == "new")
                        m.HwPurposeNewRecruitment = true;
                    else if (purpose == "replacement")
                        m.HwPurposeReplacement = true;
                    else if (purpose == "additional")
                        m.HwPurposeAdditional = true;

                    // Categories (right column)
                    switch (cat)
                    {
                        case "DesktopAllIn": m.HwDesktopAllIn = true; break;
                        case "NotebookAllIn": m.HwNotebookAllIn = true; break;
                        case "DesktopOnly": m.HwDesktopOnly = true; break;
                        case "NotebookOnly": m.HwNotebookOnly = true; break;
                        case "NotebookBattery": m.HwNotebookBattery = true; break;
                        case "PowerAdapter": m.HwPowerAdapter = true; break;
                        case "Mouse": m.HwMouse = true; break;
                        case "ExternalHDD": m.HwExternalHdd = true; break;
                        case "Other":
                            if (!string.IsNullOrWhiteSpace(h.OtherDescription))
                                m.HwOtherText = h.OtherDescription!.Trim();
                            break;
                    }
                }
            }

            // ---- Approver names + stage dates ------------------------------------
            // Build a name map for all approver IDs (null-safe)
            var approverIds = new int?[]
            {
        status.Flow?.HodUserId,
        status.Flow?.GroupItHodUserId,
        status.Flow?.FinHodUserId,
        status.Flow?.MgmtUserId
            };

            var idSet = approverIds
                .Where(i => i.HasValue).Select(i => i!.Value)
                .Distinct().ToList();

            var nameMap = await _db.Users
                .Where(u => idSet.Contains(u.Id))
                .ToDictionaryAsync(u => u.Id, u => u.FullName ?? u.UserName ?? $"User {u.Id}");

            string Name(int? id) => id.HasValue ? nameMap.GetValueOrDefault(id.Value, "") : "";

            // Push names into the report model for PDF
            m.HodApprovedBy = Name(status.Flow?.HodUserId);
            m.GitHodApprovedBy = Name(status.Flow?.GroupItHodUserId); // DB field: GroupItHodUserId
            m.FinHodApprovedBy = Name(status.Flow?.FinHodUserId);
            m.MgmtApprovedBy = Name(status.Flow?.MgmtUserId);

            // Also expose the per-stage submit dates (your PDF uses them)
            m.HodSubmitDate = status.HodSubmitDate;
            m.GitHodSubmitDate = status.GitHodSubmitDate;
            m.FinHodSubmitDate = status.FinHodSubmitDate;
            m.MgmtSubmitDate = status.MgmtSubmitDate;

            // ---- Gate: only after both acceptances -------------------------------
            if (!(b?.RequestorAccepted == true && b?.ItAccepted == true))
                return BadRequest(new { message = "PDF available after both acceptances." });

            var pdf = new ItRequestPdfService().Generate(m);
            //var fileName = $"IT_Request_{m.ItRequestId}_SectionB.pdf";
            return File(pdf, "application/pdf");
        }




        // ===== Section B: Save (IT team only, overall must be Approved) =====
        public class SectionBSaveDto
        {
            public int StatusId { get; set; }
            public string AssetNo { get; set; } = string.Empty;
            public string MachineId { get; set; } = string.Empty;
            public string IpAddress { get; set; } = string.Empty;
            public string WiredMac { get; set; } = string.Empty;
            public string WifiMac { get; set; } = string.Empty;
            public string DialupAcc { get; set; } = string.Empty;
            public string Remarks { get; set; } = string.Empty;
        }

        // REPLACE the old method with this one (same route name kept)
        [HttpGet("sectionB/approvedList")]
        public async Task<IActionResult> SectionBList([FromQuery] int month, [FromQuery] int year)
        {
            int currentUserId;
            try { currentUserId = GetCurrentUserIdOrThrow(); }
            catch (Exception ex) { return Unauthorized(new { message = ex.Message }); }

            bool isItMember = await _db.ItTeamMembers.AnyAsync(t => t.UserId == currentUserId);
            if (!isItMember) return Ok(new { isItMember = false, data = Array.Empty<object>() });

            // Pull ALL requests for the month by SubmitDate (so "not eligible yet" also appear)
            var baseQuery = _db.ItRequestStatus
            .Include(s => s.Request)
            .Where(s => s.Request != null &&
                        s.Request.SubmitDate.Month == month &&
                        s.Request.SubmitDate.Year == year)
            .Select(s => new
            {
                S = s,
                ApprovedAt = s.MgmtSubmitDate ?? s.FinHodSubmitDate ?? s.GitHodSubmitDate ?? s.HodSubmitDate
            });

            var rows = await baseQuery
                .Select(x => new
                {
                    x.S.StatusId,
                    x.S.ItRequestId,
                    StaffName = x.S.Request!.StaffName,
                    DepartmentName = x.S.Request.DepartmentName,
                    SubmitDate = x.S.Request.SubmitDate,
                    OverallStatus = x.S.OverallStatus ?? "Draft",
                    ApprovedAt = x.ApprovedAt,

                    B = _db.ItRequestAssetInfo
                        .Where(b => b.ItRequestId == x.S.ItRequestId)
                        .Select(b => new
                        {
                            saved = true,
                            b.SectionBSent,
                            b.RequestorAccepted,
                            b.ItAccepted,
                            b.RequestorAcceptedAt,
                            b.ItAcceptedAt,
                            b.ItAcceptedByName,
                            b.LastEditedAt,
                            b.LastEditedByName
                        })
                        .FirstOrDefault()
                })
                .ToListAsync();

            // Stage derivation
            string Stage(string overall, dynamic b)
            {
                bool isApproved = string.Equals(overall, "Approved", StringComparison.OrdinalIgnoreCase);

                if (!isApproved) return "NOT_ELIGIBLE";   // first part not approved yet
                if (b == null) return "PENDING";        // no Section B row yet

                bool sent = b.SectionBSent == true;
                bool req = b.RequestorAccepted == true;
                bool it = b.ItAccepted == true;

                if (req && it) return "COMPLETE";         // both accepted
                if (!sent) return "DRAFT";             // saved but not sent
                return "AWAITING";                                      // one side accepted
            }

            int Rank(string stage) => stage switch
            {
                "PENDING" => 0,
                "DRAFT" => 1,
                "AWAITING" => 2,
                "COMPLETE" => 3,
                "NOT_ELIGIBLE" => 4,
                _ => 9
            };

            var data = rows
                .Select(x =>
                {
                    var stage = Stage(x.OverallStatus, x.B);
                    return new
                    {
                        statusId = x.StatusId,
                        itRequestId = x.ItRequestId,
                        staffName = x.StaffName,
                        departmentName = x.DepartmentName,
                        submitDate = x.SubmitDate,
                        approvedAt = x.ApprovedAt,
                        overallStatus = x.OverallStatus,
                        stage,                       // <-- used by UI badges/sorting
                        stageRank = Rank(stage),     // <-- used by UI sorting

                        sb = new
                        {
                            saved = x.B != null,
                            requestorAccepted = x.B?.RequestorAccepted ?? false,
                            requestorAcceptedAt = x.B?.RequestorAcceptedAt,
                            itAccepted = x.B?.ItAccepted ?? false,
                            itAcceptedAt = x.B?.ItAcceptedAt,
                            itAcceptedBy = x.B?.ItAcceptedByName,
                            lastEditedAt = x.B?.LastEditedAt,
                            lastEditedBy = x.B?.LastEditedByName
                        }
                    };
                })
                .Where(x => x.stage != "NOT_ELIGIBLE")
                .OrderBy(x => x.stageRank)
                .ThenByDescending(x => x.approvedAt ?? x.submitDate)
                .ToList();

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




        [HttpPost("sectionB/save")]
        public async Task<IActionResult> SectionBSave([FromBody] SectionBSaveDto dto)
        {
            int currentUserId;
            try { currentUserId = GetCurrentUserIdOrThrow(); } catch (Exception ex) { return Unauthorized(ex.Message); }

            var status = await _db.ItRequestStatus
                .Include(s => s.Request)
                .FirstOrDefaultAsync(s => s.StatusId == dto.StatusId);

            if (status == null) return NotFound("Status not found");
            if ((status.OverallStatus ?? "Pending") != "Approved")
                return BadRequest(new { message = "Section B is available only after OverallStatus is Approved." });

            bool isItMember = await _db.ItTeamMembers.AnyAsync(t => t.UserId == currentUserId);
            if (!isItMember) return Unauthorized("Only IT Team members can edit Section B.");

            // basic server-side validation (at least one identifier)
            List<string> errors = new();
            if (string.IsNullOrWhiteSpace(dto.AssetNo) &&
                string.IsNullOrWhiteSpace(dto.MachineId) &&
                string.IsNullOrWhiteSpace(dto.IpAddress))
                errors.Add("Provide at least one of: Asset No, Machine ID, or IP Address.");
            if (errors.Count > 0) return BadRequest(new { message = string.Join(" ", errors) });

            var existing = await _db.ItRequestAssetInfo.FirstOrDefaultAsync(x => x.ItRequestId == status.ItRequestId);
            if (existing != null && existing.SectionBSent)
                return BadRequest(new { message = "This Section B was sent. It can’t be edited anymore." });

            var editor = await _db.Users.FirstOrDefaultAsync(u => u.Id == currentUserId);
            var editorName = editor?.FullName ?? editor?.UserName ?? $"User {currentUserId}";
            var now = DateTime.Now;

            try
            {
                if (existing == null)
                {
                    existing = new ItRequestAssetInfo
                    {
                        ItRequestId = status.ItRequestId,
                        AssetNo = dto.AssetNo?.Trim(),
                        MachineId = dto.MachineId?.Trim(),
                        IpAddress = dto.IpAddress?.Trim(),
                        WiredMac = dto.WiredMac?.Trim(),
                        WifiMac = dto.WifiMac?.Trim(),
                        DialupAcc = dto.DialupAcc?.Trim(),
                        Remarks = dto.Remarks?.Trim(),
                        LastEditedAt = now,
                        LastEditedByUserId = currentUserId,
                        LastEditedByName = editorName,
                        SectionBSent = false,
                        SectionBSentAt = null,
                        // ensure accept flags are clear on draft save
                        RequestorAccepted = false,
                        RequestorAcceptedAt = null,
                        ItAccepted = false,
                        ItAcceptedAt = null,
                        ItAcceptedByUserId = null,
                        ItAcceptedByName = null
                    };
                    _db.ItRequestAssetInfo.Add(existing);
                }
                else
                {
                    existing.AssetNo = dto.AssetNo?.Trim();
                    existing.MachineId = dto.MachineId?.Trim();
                    existing.IpAddress = dto.IpAddress?.Trim();
                    existing.WiredMac = dto.WiredMac?.Trim();
                    existing.WifiMac = dto.WifiMac?.Trim();
                    existing.DialupAcc = dto.DialupAcc?.Trim();
                    existing.Remarks = dto.Remarks?.Trim();
                    existing.LastEditedAt = now;
                    existing.LastEditedByUserId = currentUserId;
                    existing.LastEditedByName = editorName;

                    // Saving draft always keeps it editable (not sent)
                    existing.SectionBSent = false;
                    existing.SectionBSentAt = null;

                    // if someone had previously accepted, a new draft save clears them
                    existing.RequestorAccepted = false;
                    existing.RequestorAcceptedAt = null;
                    existing.ItAccepted = false;
                    existing.ItAcceptedAt = null;
                    existing.ItAcceptedByUserId = null;
                    existing.ItAcceptedByName = null;
                }

                await _db.SaveChangesAsync();
                return Ok(new { message = "Draft saved." });
            }
            catch (DbUpdateException dbx)
            {
                return StatusCode(400, new { message = "Validation/DB error while saving Section B.", detail = dbx.InnerException?.Message ?? dbx.Message });
            }
            catch (Exception ex)
            {
                return StatusCode(500, new { message = "Server error while saving Section B.", detail = ex.Message });
            }
        }

        public class SectionBSendDto
        {
            public int StatusId { get; set; }
            public string? AssetNo { get; set; }
            public string? MachineId { get; set; }
            public string? IpAddress { get; set; }
            public string? WiredMac { get; set; }
            public string? WifiMac { get; set; }
            public string? DialupAcc { get; set; }
            public string? Remarks { get; set; }
        }


        [HttpPost("sectionB/send")]
        public async Task<IActionResult> SectionBSend([FromBody] SectionBSendDto dto)
        {
            int currentUserId;
            try { currentUserId = GetCurrentUserIdOrThrow(); } catch (Exception ex) { return Unauthorized(ex.Message); }

            var status = await _db.ItRequestStatus
                .Include(s => s.Request)
                .FirstOrDefaultAsync(s => s.StatusId == dto.StatusId);

            if (status == null) return NotFound(new { message = "Status not found" });
            if ((status.OverallStatus ?? "Pending") != "Approved")
                return BadRequest(new { message = "Section B can be sent only after OverallStatus is Approved." });

            bool isItMember = await _db.ItTeamMembers.AnyAsync(t => t.UserId == currentUserId);
            if (!isItMember) return Unauthorized(new { message = "Only IT can send Section B." });

            var b = await _db.ItRequestAssetInfo.FirstOrDefaultAsync(x => x.ItRequestId == status.ItRequestId);
            if (b?.SectionBSent == true)
                return BadRequest(new { message = "Section B already sent." });

            var editor = await _db.Users.FirstOrDefaultAsync(u => u.Id == currentUserId);
            var editorName = editor?.FullName ?? editor?.UserName ?? $"User {currentUserId}";
            var now = DateTime.Now;

            if (b == null)
            {
                b = new ItRequestAssetInfo
                {
                    ItRequestId = status.ItRequestId,
                    RequestorAccepted = false,
                    RequestorAcceptedAt = null,
                    ItAccepted = false,
                    ItAcceptedAt = null,
                    ItAcceptedByUserId = null,
                    ItAcceptedByName = null
                };
                _db.ItRequestAssetInfo.Add(b);
            }

            // Apply incoming fields (allow send without prior draft)
            if (dto.AssetNo != null) b.AssetNo = dto.AssetNo.Trim();
            if (dto.MachineId != null) b.MachineId = dto.MachineId.Trim();
            if (dto.IpAddress != null) b.IpAddress = dto.IpAddress.Trim();
            if (dto.WiredMac != null) b.WiredMac = dto.WiredMac.Trim();
            if (dto.WifiMac != null) b.WifiMac = dto.WifiMac.Trim();
            if (dto.DialupAcc != null) b.DialupAcc = dto.DialupAcc.Trim();
            if (dto.Remarks != null) b.Remarks = dto.Remarks.Trim();

            b.LastEditedAt = now;
            b.LastEditedByUserId = currentUserId;
            b.LastEditedByName = editorName;

            // Require at least one identifier
            if (string.IsNullOrWhiteSpace(b.AssetNo) &&
                string.IsNullOrWhiteSpace(b.MachineId) &&
                string.IsNullOrWhiteSpace(b.IpAddress))
                return BadRequest(new { message = "Provide at least Asset No / Machine ID / IP Address before sending." });

            b.SectionBSent = true;
            b.SectionBSentAt = now;

            await _db.SaveChangesAsync();
            return Ok(new { message = "Section B sent. It is now locked for editing until both acceptances complete." });
        }


        public class SectionBResetDto { public int StatusId { get; set; } }

        [HttpPost("sectionB/reset")]
        public async Task<IActionResult> SectionBReset([FromBody] SectionBResetDto dto)
        {
            int currentUserId;
            try { currentUserId = GetCurrentUserIdOrThrow(); } catch (Exception ex) { return Unauthorized(ex.Message); }

            bool isItMember = await _db.ItTeamMembers.AnyAsync(t => t.UserId == currentUserId);
            if (!isItMember) return Unauthorized(new { message = "Only IT can reset Section B." });

            var status = await _db.ItRequestStatus
                .Include(s => s.Request)
                .FirstOrDefaultAsync(s => s.StatusId == dto.StatusId);
            if (status == null) return NotFound(new { message = "Status not found" });

            var b = await _db.ItRequestAssetInfo.FirstOrDefaultAsync(x => x.ItRequestId == status.ItRequestId);
            if (b == null) return Ok(new { message = "Nothing to reset." });

            if (b.SectionBSent)
                return BadRequest(new { message = "Cannot reset after Section B was sent." });

            b.AssetNo = b.MachineId = b.IpAddress = b.WiredMac = b.WifiMac = b.DialupAcc = b.Remarks = null;
            b.RequestorAccepted = false; b.RequestorAcceptedAt = null;
            b.ItAccepted = false; b.ItAcceptedAt = null; b.ItAcceptedByUserId = null; b.ItAcceptedByName = null;
            b.LastEditedAt = DateTime.Now;
            b.LastEditedByUserId = currentUserId;
            b.LastEditedByName = (await _db.Users.FirstOrDefaultAsync(u => u.Id == currentUserId))?.FullName;

            await _db.SaveChangesAsync();
            return Ok(new { message = "Section B reset to empty draft." });
        }



        // ===== Section B: Accept (REQUESTOR or IT) =====
        public class SectionBAcceptDto { public int StatusId { get; set; } public string By { get; set; } = string.Empty; } // REQUESTOR | IT

        [HttpPost("sectionB/accept")]
        public async Task<IActionResult> SectionBAccept([FromBody] SectionBAcceptDto dto)
        {
            int currentUserId;
            try { currentUserId = GetCurrentUserIdOrThrow(); } catch (Exception ex) { return Unauthorized(ex.Message); }

            var status = await _db.ItRequestStatus
                .Include(s => s.Request)
                .FirstOrDefaultAsync(s => s.StatusId == dto.StatusId);
            if (status == null) return NotFound("Status not found");

            var sectionB = await _db.ItRequestAssetInfo.FirstOrDefaultAsync(x => x.ItRequestId == status.ItRequestId);
            if (sectionB == null) return BadRequest(new { message = "Save Section B before acceptance." });

            if (!sectionB.SectionBSent)
                return BadRequest(new { message = "Acceptances are only available after Section B is sent." });

            var now = DateTime.Now;
            var actor = await _db.Users.FirstOrDefaultAsync(u => u.Id == currentUserId);
            var actorName = actor?.FullName ?? actor?.UserName ?? $"User {currentUserId}";

            if (string.Equals(dto.By, "REQUESTOR", StringComparison.OrdinalIgnoreCase))
            {
                if (status.Request.UserId != currentUserId)
                    return Unauthorized("Only the requestor can perform this acceptance.");
                if (sectionB.RequestorAccepted)
                    return BadRequest(new { message = "Requestor already accepted." });

                sectionB.RequestorAccepted = true;
                sectionB.RequestorAcceptedAt = now;
            }
            else if (string.Equals(dto.By, "IT", StringComparison.OrdinalIgnoreCase))
            {
                bool isItMember = await _db.ItTeamMembers.AnyAsync(t => t.UserId == currentUserId);
                if (!isItMember) return Unauthorized("Only IT Team members can accept as IT.");
                if (sectionB.ItAccepted)
                    return BadRequest(new { message = "IT already accepted." });

                sectionB.ItAccepted = true;
                sectionB.ItAcceptedAt = now;
                sectionB.ItAcceptedByUserId = currentUserId;
                sectionB.ItAcceptedByName = actorName;
            }
            else
            {
                return BadRequest(new { message = "Unknown acceptance type." });
            }

            await _db.SaveChangesAsync();
            return Ok(new { message = "Accepted." });
        }


        // ===== IT Team maintenance (simple stub) =====
        // GET: /ItRequestAPI/itTeam
        [HttpGet("itTeam")]
        public async Task<IActionResult> GetItTeam() =>
            Ok(await _db.ItTeamMembers.Select(t => t.UserId).ToListAsync());

        // POST: /ItRequestAPI/itTeam   body: { userIds: [1,2,3] }
        public class ItTeamDto { public List<int> UserIds { get; set; } = new(); }
        [HttpPost("itTeam")]
        public async Task<IActionResult> SetItTeam([FromBody] ItTeamDto dto)
        {
            // Add your own admin authorization guard here
            var all = _db.ItTeamMembers;
            _db.ItTeamMembers.RemoveRange(all);
            await _db.SaveChangesAsync();

            foreach (var uid in dto.UserIds.Distinct())
                _db.ItTeamMembers.Add(new ItTeamMember { UserId = uid });

            await _db.SaveChangesAsync();
            return Ok(new { message = "IT Team updated." });
        }


        // --------------------------------------------------------------------
        // GET: /ItRequestAPI/me
        // Pull snapshot from aspnetusers -> departments -> companies
        // Used by Create.cshtml to prefill read-only requester details
        // --------------------------------------------------------------------
        [HttpGet("me")]
        public async Task<IActionResult> Me()
        {
            var userIdStr = _userManager.GetUserId(User);
            if (string.IsNullOrEmpty(userIdStr) || !int.TryParse(userIdStr, out int currentUserId))
                return Unauthorized("Invalid user");

            var user = await _db.Users.FirstOrDefaultAsync(u => u.Id == currentUserId);
            if (user == null) return NotFound("User not found");

            var dept = await _db.Departments.FirstOrDefaultAsync(d => d.DepartmentId == user.departmentId);
            var comp = dept != null
    ? await _db.Companies.FirstOrDefaultAsync(c => c.CompanyId == dept.CompanyId)
    : null;

            return Ok(new
            {
                userId = user.Id,
                staffName = user.FullName ?? user.UserName,
                departmentId = dept?.DepartmentId,
                departmentName = dept?.DepartmentName ?? "",
                companyId = comp?.CompanyId,
                companyName = comp?.CompanyName ?? "",
                designation = "",             // adjust if you actually store this somewhere
                location = "",
                employmentStatus = "",
                contractEndDate = (DateTime?)null,
                phoneExt = user.PhoneNumber ?? ""
            });
        }
        #region Approval razor
        // --------------------------------------------------------------------
        // GET: /ItRequestAPI/pending?month=9&year=2025
        // --------------------------------------------------------------------
        [HttpGet("pending")]
        public async Task<IActionResult> GetPending(int month, int year)
        {
            var userIdStr = _userManager.GetUserId(User);
            if (string.IsNullOrEmpty(userIdStr) || !int.TryParse(userIdStr, out int currentUserId))
                return Unauthorized("Invalid or missing user ID");

            // Which flows is this user part of, and what role in each?
            var flows = await _db.ItApprovalFlows
                .Where(f => f.HodUserId == currentUserId ||
                            f.GroupItHodUserId == currentUserId ||
                            f.FinHodUserId == currentUserId ||
                            f.MgmtUserId == currentUserId)
                .ToListAsync();

            if (!flows.Any())
                return Ok(new { roles = Array.Empty<string>(), data = Array.Empty<object>() });

            var flowRoleMap = flows.ToDictionary(
                f => f.ItApprovalFlowId,
                f => f.HodUserId == currentUserId ? "HOD" :
                     f.GroupItHodUserId == currentUserId ? "GIT_HOD" :
                     f.FinHodUserId == currentUserId ? "FIN_HOD" : "MGMT"
            );

            var statuses = await _db.ItRequestStatus
                .Include(s => s.Request)
                .Where(s => s.Request != null &&
            s.Request.SubmitDate.Month == month
                         && s.Request.SubmitDate.Year == year
                         && (s.OverallStatus ?? "Draft") != "Draft" // hide drafts
                         && s.OverallStatus != "Cancelled")
                .ToListAsync();

            var results = statuses
                .Where(s => flowRoleMap.ContainsKey(s.ItApprovalFlowId))
                .Select(s =>
                {
                    var role = flowRoleMap[s.ItApprovalFlowId];

                    // quick flags
                    bool hodApproved = s.HodStatus == "Approved";
                    bool gitApproved = s.GitHodStatus == "Approved";
                    bool finApproved = s.FinHodStatus == "Approved";
                    bool anyRejected = (s.HodStatus == "Rejected" ||
                                        s.GitHodStatus == "Rejected" ||
                                        s.FinHodStatus == "Rejected" ||
                                        s.MgmtStatus == "Rejected");

                    string currentUserStatus = "N/A";
                    bool canApprove = false;

                    if (role == "HOD")
                    {
                        currentUserStatus = s.HodStatus ?? "Pending";
                        canApprove = (currentUserStatus == "Pending"); // first approver always sees its own pending
                    }
                    else if (role == "GIT_HOD")
                    {
                        // only after HOD approved and not previously rejected
                        if (!anyRejected && hodApproved)
                        {
                            currentUserStatus = s.GitHodStatus ?? "Pending";
                            canApprove = (currentUserStatus == "Pending");
                        }
                        else
                        {
                            // don’t surface “pending” for future approver
                            currentUserStatus = (s.GitHodStatus ?? "N/A");
                        }
                    }
                    else if (role == "FIN_HOD")
                    {
                        if (!anyRejected && hodApproved && gitApproved)
                        {
                            currentUserStatus = s.FinHodStatus ?? "Pending";
                            canApprove = (currentUserStatus == "Pending");
                        }
                        else
                        {
                            currentUserStatus = (s.FinHodStatus ?? "N/A");
                        }
                    }
                    else // MGMT
                    {
                        if (!anyRejected && hodApproved && gitApproved && finApproved)
                        {
                            currentUserStatus = s.MgmtStatus ?? "Pending";
                            canApprove = (currentUserStatus == "Pending");
                        }
                        else
                        {
                            currentUserStatus = (s.MgmtStatus ?? "N/A");
                        }
                    }

                    // include row ONLY IF:
                    //  - this approver can act now (pending for them), OR
                    //  - this approver already decided (for Completed tab)
                    bool includeForUser =
                        canApprove ||
                        currentUserStatus == "Approved" ||
                        currentUserStatus == "Rejected";

                    return new
                    {
                        s.StatusId,
                        s.ItRequestId,
                        s.Request.StaffName,
                        s.Request.DepartmentName,
                        SubmitDate = s.Request.SubmitDate,
                        s.HodStatus,
                        s.GitHodStatus,
                        s.FinHodStatus,
                        s.MgmtStatus,
                        OverallStatus = s.OverallStatus,
                        Role = role,
                        CurrentUserStatus = currentUserStatus,
                        CanApprove = canApprove,
                        IsOverallRejected = anyRejected
                    };
                })
                .Where(r => r != null && (r.CanApprove || r.CurrentUserStatus == "Approved" || r.CurrentUserStatus == "Rejected"))
                .ToList();

            return Ok(new
            {
                roles = flowRoleMap.Values.Distinct().ToList(),
                data = results
            });
        }



        // --------------------------------------------------------------------
        // POST: /ItRequestAPI/approveReject
        // --------------------------------------------------------------------
        public class ApproveRejectDto
        {
            public int StatusId { get; set; }
            public string Decision { get; set; } = string.Empty; // "Approved" or "Rejected"
            public string? Comment { get; set; }
        }

        [HttpPost("approveReject")]
        public async Task<IActionResult> ApproveReject([FromBody] ApproveRejectDto dto)
        {
            if (dto == null || string.IsNullOrWhiteSpace(dto.Decision))
                return BadRequest(new { message = "Invalid request" });

            var userIdStr = _userManager.GetUserId(User);
            if (string.IsNullOrEmpty(userIdStr) || !int.TryParse(userIdStr, out int currentUserId))
                return Unauthorized("Invalid user");

            var status = await _db.ItRequestStatus
                .Include(s => s.Flow)
                .Include(s => s.Request)
                .FirstOrDefaultAsync(s => s.StatusId == dto.StatusId);

            if (status == null) return NotFound("Status not found");

            var overall = status.OverallStatus ?? "Draft";

            // Drafts cannot be approved/rejected ever
            if (overall == "Draft")
                return StatusCode(409, new { message = "This request isn’t submitted yet. Approvals are only allowed once it is Pending." });

            // CANCELLED: hard stop
            if (string.Equals(overall, "Cancelled", StringComparison.OrdinalIgnoreCase))
                return StatusCode(409, new { message = "This request has been Cancelled and cannot be approved or rejected." });

            // If any earlier stage already rejected, block
            if (status.HodStatus == "Rejected" || status.GitHodStatus == "Rejected" ||
                status.FinHodStatus == "Rejected" || status.MgmtStatus == "Rejected")
                return BadRequest(new { message = "Request already rejected by a previous approver." });

            var now = DateTime.Now;
            string decision = dto.Decision.Trim();

            if (status.Flow.HodUserId == currentUserId && status.HodStatus == "Pending")
            {
                status.HodStatus = decision; status.HodSubmitDate = now;
            }
            else if (status.Flow.GroupItHodUserId == currentUserId && status.GitHodStatus == "Pending" && status.HodStatus == "Approved")
            {
                status.GitHodStatus = decision; status.GitHodSubmitDate = now;
            }
            else if (status.Flow.FinHodUserId == currentUserId && status.FinHodStatus == "Pending" &&
                     status.HodStatus == "Approved" && status.GitHodStatus == "Approved")
            {
                status.FinHodStatus = decision; status.FinHodSubmitDate = now;
            }
            else if (status.Flow.MgmtUserId == currentUserId && status.MgmtStatus == "Pending" &&
                     status.HodStatus == "Approved" && status.GitHodStatus == "Approved" && status.FinHodStatus == "Approved")
            {
                status.MgmtStatus = decision; status.MgmtSubmitDate = now;
            }
            else
            {
                return BadRequest(new { message = "Not authorized to act at this stage." });
            }

            if (decision == "Rejected")
                status.OverallStatus = "Rejected";
            else if (status.HodStatus == "Approved" && status.GitHodStatus == "Approved" &&
                     status.FinHodStatus == "Approved" && status.MgmtStatus == "Approved")
                status.OverallStatus = "Approved";

            await _db.SaveChangesAsync();
            return Ok(new { message = "Decision recorded successfully." });
        }

        #endregion

        // --------------------------------------------------------------------
        // GET: /ItRequestAPI/myRequests
        // --------------------------------------------------------------------
        [HttpGet("myRequests")]
        public async Task<IActionResult> MyRequests([FromQuery] string? status = null,
                                                    [FromQuery] DateTime? from = null,
                                                    [FromQuery] DateTime? to = null,
                                                    [FromQuery] int page = 1,
                                                    [FromQuery] int pageSize = 50)
        {
            var userIdStr = _userManager.GetUserId(User);
            if (string.IsNullOrEmpty(userIdStr) || !int.TryParse(userIdStr, out int userId))
                return Unauthorized("Invalid user");

            var q = _db.ItRequests
                .Join(_db.ItRequestStatus, r => r.ItRequestId, s => s.ItRequestId, (r, s) => new { r, s })
                .Where(x => x.r.UserId == userId)
                .AsQueryable();

            if (!string.IsNullOrWhiteSpace(status))
                q = q.Where(x => x.s.OverallStatus == status);

            if (from.HasValue) q = q.Where(x => x.r.SubmitDate >= from.Value);
            if (to.HasValue) q = q.Where(x => x.r.SubmitDate < to.Value.AddDays(1));

            var total = await q.CountAsync();
            var data = await q
    .OrderByDescending(x => x.r.SubmitDate)
    .Skip((page - 1) * pageSize)
    .Take(pageSize)
    .Select(x => new
    {
        x.r.ItRequestId,
        x.s.StatusId,
        x.r.StaffName,
        x.r.DepartmentName,
        x.r.CompanyName,
        x.r.RequiredDate,
        x.r.SubmitDate,
        OverallStatus = x.s.OverallStatus,

        // extra fields used by UI action buttons
        IsLockedForEdit = x.r.IsLockedForEdit,
        EditableUntil = x.r.EditableUntil,
        HodStatus = x.s.HodStatus,
        GitHodStatus = x.s.GitHodStatus,
        FinHodStatus = x.s.FinHodStatus,
        MgmtStatus = x.s.MgmtStatus
    })
    .ToListAsync();

            // post-shape with flags (kept in-memory to keep SQL simple)
            var shaped = data.Select(d =>
            {
                var overall = d.OverallStatus ?? "Draft";
                var remaining = d.EditableUntil.HasValue
                    ? Math.Max(0, (int)(d.EditableUntil.Value - DateTime.UtcNow).TotalSeconds)
                    : 0;

                bool isDraft = overall == "Draft";
                bool canEditDraft = isDraft && !d.IsLockedForEdit && remaining > 0;

                bool allPending =
                    (d.HodStatus ?? "Pending") == "Pending" &&
                    (d.GitHodStatus ?? "Pending") == "Pending" &&
                    (d.FinHodStatus ?? "Pending") == "Pending" &&
                    (d.MgmtStatus ?? "Pending") == "Pending";

                bool canCancel = isDraft || (ALLOW_PENDING_CANCEL && overall == "Pending" && allPending);


                // optional: hint URL for your "View" button in Draft tab
                var editUrlHint = $"/IT/ApprovalDashboard/Edit?statusId={d.StatusId}";

                return new
                {
                    d.ItRequestId,
                    d.StatusId,
                    d.StaffName,
                    d.DepartmentName,
                    d.CompanyName,
                    d.RequiredDate,
                    d.SubmitDate,
                    OverallStatus = overall,
                    canEditDraft,
                    canCancel,
                    editUrlHint,
                    remainingSeconds = remaining
                };
            }).ToList();

            return Ok(new { total, page, pageSize, data = shaped });

        }

        // --------------------------------------------------------------------
        // Flows CRUD
        // --------------------------------------------------------------------
        [HttpGet("flows")]
        public async Task<IActionResult> GetFlows()
        {
            var flows = await _db.ItApprovalFlows.ToListAsync();
            return Ok(flows);
        }

        [HttpPost("flows")]
        public async Task<IActionResult> CreateFlow([FromBody] ItApprovalFlow flow)
        {
            if (string.IsNullOrWhiteSpace(flow.FlowName))
                return BadRequest(new { message = "Flow name is required" });

            _db.ItApprovalFlows.Add(flow);
            await _db.SaveChangesAsync();
            return Ok(new { message = "Flow created" });
        }

        [HttpPut("flows/{id}")]
        public async Task<IActionResult> UpdateFlow(int id, [FromBody] ItApprovalFlow updated)
        {
            var existing = await _db.ItApprovalFlows.FindAsync(id);
            if (existing == null) return NotFound();

            existing.FlowName = updated.FlowName;
            existing.HodUserId = updated.HodUserId;
            existing.GroupItHodUserId = updated.GroupItHodUserId;
            existing.FinHodUserId = updated.FinHodUserId;
            existing.MgmtUserId = updated.MgmtUserId;

            await _db.SaveChangesAsync();
            return Ok(new { message = "Flow updated" });
        }

        [HttpDelete("flows/{id}")]
        public async Task<IActionResult> DeleteFlow(int id)
        {
            var flow = await _db.ItApprovalFlows.FindAsync(id);
            if (flow == null) return NotFound();

            bool inUse = await _db.ItRequestStatus.AnyAsync(s => s.ItApprovalFlowId == id);
            if (inUse) return BadRequest(new { message = "Cannot delete flow; it is in use." });

            _db.ItApprovalFlows.Remove(flow);
            await _db.SaveChangesAsync();
            return Ok(new { message = "Flow deleted" });
        }

        // --------------------------------------------------------------------
        // DTOs for create
        // --------------------------------------------------------------------
        public class CreateItRequestDto
        {
            // UserId ignored by server (derived from token)
            public string StaffName { get; set; } = string.Empty;       // client may send, server will override
            public string? CompanyName { get; set; }        // override
            public string? DepartmentName { get; set; }     // override

            public string? Designation { get; set; }
            public string? Location { get; set; }
            public string? EmploymentStatus { get; set; }
            public DateTime? ContractEndDate { get; set; }
            public DateTime RequiredDate { get; set; }
            public string? PhoneExt { get; set; }

            public List<HardwareDto> Hardware { get; set; } = new();
            public List<EmailDto> Emails { get; set; } = new();
            public List<OsreqDto> OSReqs { get; set; } = new();
            public List<SoftwareDto> Software { get; set; } = new();
            public List<SharedPermDto> SharedPerms { get; set; } = new();

            public int? EditWindowHours { get; set; }
            public bool? SendNow { get; set; }
        }

        public class HardwareDto
        {
            public string Category { get; set; } = "";
            public string? Purpose { get; set; }
            public string? Justification { get; set; }
            public string? OtherDescription { get; set; }
        }

        // NOTE: as requested, Email UI only collects ProposedAddress.
        // Purpose/Notes will be stored as nulls.
        public class EmailDto
        {
            public string? ProposedAddress { get; set; }
        }

        public class OsreqDto
        {
            public string? RequirementText { get; set; }
        }

        public class SoftwareDto
        {
            public string Bucket { get; set; } = "";    // General | Utility | Custom
            public string Name { get; set; } = "";
            public string? OtherName { get; set; }
            public string? Notes { get; set; }
        }

        public class SharedPermDto
        {
            public string? ShareName { get; set; }
            public bool CanRead { get; set; }
            public bool CanWrite { get; set; }
            public bool CanDelete { get; set; }
            public bool CanRemove { get; set; }
        }


        // --------------------------------------------------------------------
        // POST: /ItRequestAPI/create
        // Creates a new IT Request and starts an edit window (default 24 hours)
        // --------------------------------------------------------------------
        [HttpPost("create")]
        public async Task<IActionResult> CreateRequest([FromBody] CreateItRequestDto dto)
        {
            if (dto == null)
                return BadRequest(new { message = "Invalid request payload" });

            if (dto.RequiredDate == default)
                return BadRequest(new { message = "RequiredDate is required." });

            // --- Server-side guard: RequiredDate >= today + 7 (local server date) ---
            var minDate = DateTime.Today.AddDays(7);
            if (dto.RequiredDate.Date < minDate)
                return BadRequest(new { message = $"RequiredDate must be at least {minDate:yyyy-MM-dd} or later." });

            // --- Server-side guard: SharedPerms cap 6 (trim or reject; here we trim silently) ---
            if (dto.SharedPerms != null && dto.SharedPerms.Count > 6)
                dto.SharedPerms = dto.SharedPerms.Take(6).ToList();

            // --- Optional normalization: avoid conflicting hardware choices ---
            // If DesktopAllIn is present, drop NotebookAllIn/NotebookOnly
            bool hasDesktopAllIn = dto.Hardware?.Any(h => string.Equals(h.Category, "DesktopAllIn", StringComparison.OrdinalIgnoreCase)) == true;
            if (hasDesktopAllIn && dto.Hardware != null)
                dto.Hardware = dto.Hardware.Where(h => !string.Equals(h.Category, "NotebookAllIn", StringComparison.OrdinalIgnoreCase)
                                                    && !string.Equals(h.Category, "NotebookOnly", StringComparison.OrdinalIgnoreCase))
                                           .ToList();

            // If NotebookAllIn is present, drop DesktopAllIn/DesktopOnly
            bool hasNotebookAllIn = dto.Hardware?.Any(h => string.Equals(h.Category, "NotebookAllIn", StringComparison.OrdinalIgnoreCase)) == true;
            if (hasNotebookAllIn && dto.Hardware != null)
                dto.Hardware = dto.Hardware.Where(h => !string.Equals(h.Category, "DesktopAllIn", StringComparison.OrdinalIgnoreCase)
                                                    && !string.Equals(h.Category, "DesktopOnly", StringComparison.OrdinalIgnoreCase))
                                           .ToList();

            try
            {
                // --- Validate and get current user -----------------------------------------------------
                var userIdStr = _userManager.GetUserId(User);
                if (string.IsNullOrEmpty(userIdStr) || !int.TryParse(userIdStr, out int currentUserId))
                    return Unauthorized("Invalid user");

                // --- Lookup user snapshot --------------------------------------------------------------
                var user = await _db.Users.FirstOrDefaultAsync(u => u.Id == currentUserId);
                if (user == null) return NotFound("User not found.");

                var dept = await _db.Departments.FirstOrDefaultAsync(d => d.DepartmentId == user.departmentId);
                var comp = dept != null
                    ? await _db.Companies.FirstOrDefaultAsync(c => c.CompanyId == dept.CompanyId)
                    : null;

                var snapStaffName = user.FullName ?? user.UserName ?? "(unknown)";
                var snapDepartmentName = dept?.DepartmentName ?? "";
                var snapCompanyName = comp?.CompanyName ?? "";

                // --- Determine edit window hours -------------------------------------------------------
                int windowHours = 24;
                if (dto.EditWindowHours.HasValue)
                    windowHours = Math.Max(1, Math.Min(dto.EditWindowHours.Value, 24 * 14));
                else if (int.TryParse(Request.Headers["X-Edit-Window-Hours"], out var h))
                    windowHours = Math.Max(1, Math.Min(h, 24 * 14));
                else if (int.TryParse(Request.Query["editWindowHours"], out var q))
                    windowHours = Math.Max(1, Math.Min(q, 24 * 14));

                var nowUtc = DateTime.UtcNow;

                // --- Insert main request ---------------------------------------------------------------
                var request = new ItRequest
                {
                    UserId = currentUserId,

                    StaffName = snapStaffName,
                    DepartmentName = snapDepartmentName,
                    CompanyName = snapCompanyName,

                    Designation = dto.Designation,
                    Location = dto.Location,
                    EmploymentStatus = dto.EmploymentStatus,
                    ContractEndDate = dto.ContractEndDate,

                    RequiredDate = dto.RequiredDate,
                    PhoneExt = dto.PhoneExt,
                    SubmitDate = DateTime.Now,

                    FirstSubmittedAt = nowUtc,
                    EditableUntil = nowUtc.AddHours(windowHours),
                    IsLockedForEdit = false
                };

                _db.ItRequests.Add(request);
                await _db.SaveChangesAsync();

                // --- Children --------------------------------------------------------------------------
                if (dto.Hardware != null)
                    foreach (var hw in dto.Hardware)
                        _db.ItRequestHardwares.Add(new ItRequestHardware
                        {
                            ItRequestId = request.ItRequestId,
                            Category = hw.Category,
                            Purpose = hw.Purpose,
                            Justification = hw.Justification,
                            OtherDescription = hw.OtherDescription
                        });

                if (dto.Emails != null)
                    foreach (var em in dto.Emails)
                        _db.ItRequestEmails.Add(new ItRequestEmail
                        {
                            ItRequestId = request.ItRequestId,
                            Purpose = null,
                            ProposedAddress = em.ProposedAddress,
                            Notes = null
                        });

                if (dto.OSReqs != null)
                    foreach (var os in dto.OSReqs)
                        _db.ItRequestOsRequirement.Add(new ItRequestOsRequirement
                        {
                            ItRequestId = request.ItRequestId,
                            RequirementText = os.RequirementText
                        });

                if (dto.Software != null)
                    foreach (var sw in dto.Software)
                        _db.ItRequestSoftware.Add(new ItRequestSoftware
                        {
                            ItRequestId = request.ItRequestId,
                            Bucket = sw.Bucket,
                            Name = sw.Name,
                            OtherName = sw.OtherName,
                            Notes = sw.Notes
                        });

                if (dto.SharedPerms != null)
                    foreach (var sp in dto.SharedPerms)
                        _db.ItRequestSharedPermission.Add(new ItRequestSharedPermission
                        {
                            ItRequestId = request.ItRequestId,
                            ShareName = sp.ShareName,
                            CanRead = sp.CanRead,
                            CanWrite = sp.CanWrite,
                            CanDelete = sp.CanDelete,
                            CanRemove = sp.CanRemove
                        });

                await _db.SaveChangesAsync();

                // --- Default approval flow -------------------------------------------------------------
                var flow = await _db.ItApprovalFlows.FirstOrDefaultAsync();
                if (flow == null)
                    return BadRequest(new { message = "No IT Approval Flow configured" });

                var status = new ItRequestStatus
                {
                    ItRequestId = request.ItRequestId,
                    ItApprovalFlowId = flow.ItApprovalFlowId,
                    HodStatus = "Pending",
                    GitHodStatus = "Pending",
                    FinHodStatus = "Pending",
                    MgmtStatus = "Pending",
                    OverallStatus = "Draft"
                };

                _db.ItRequestStatus.Add(status);
                await _db.SaveChangesAsync();

                if (dto.SendNow == true)
                {
                    request.IsLockedForEdit = true;
                    status.OverallStatus = "Pending";
                    await _db.SaveChangesAsync();
                }

                return Ok(new
                {
                    message = "IT request created successfully. You can edit it for a limited time.",
                    requestId = request.ItRequestId,
                    statusId = status.StatusId,
                    editableUntil = request.EditableUntil,
                    editWindowHours = windowHours,
                    overallStatus = status.OverallStatus
                });
            }
            catch (UnauthorizedAccessException ex)
            {
                return Unauthorized(ex.Message);
            }
            catch (Exception ex)
            {
                return StatusCode(500, new { message = "Error creating IT request", detail = ex.Message });
            }
        }



        private static bool ShouldLock(ItRequest r) =>
    !r.IsLockedForEdit && r.EditableUntil.HasValue && DateTime.UtcNow > r.EditableUntil.Value;

        private async Task LockOnlyAsync(ItRequest r)
        {
            if (r.IsLockedForEdit) return;
            r.IsLockedForEdit = true;
            await _db.SaveChangesAsync();
        }


        // read current edit state (also lazy-lock if expired)
        [HttpGet("editWindow/{statusId:int}")]
        public async Task<IActionResult> GetEditWindow(int statusId)
        {
            var s = await _db.ItRequestStatus.Include(x => x.Request).FirstOrDefaultAsync(x => x.StatusId == statusId);
            if (s == null) return NotFound("Status not found");
            var r = s.Request;

            if (ShouldLock(r)) await LockOnlyAsync(r);

            var remaining = r.EditableUntil.HasValue ? Math.Max(0, (int)(r.EditableUntil.Value - DateTime.UtcNow).TotalSeconds) : 0;
            return Ok(new
            {
                overallStatus = s.OverallStatus ?? "Draft",
                isEditable = !r.IsLockedForEdit && (s.OverallStatus ?? "Draft") == "Draft" && remaining > 0,
                remainingSeconds = remaining,
                editableUntil = r.EditableUntil
            });
        }

        // PUT: full draft update (same shape as create)
        public class UpdateDraftDto : CreateItRequestDto { public int StatusId { get; set; } }

        [HttpPut("edit/{statusId:int}")]
        public async Task<IActionResult> UpdateDraft(int statusId, [FromBody] UpdateDraftDto dto)
        {
            int currentUserId; try { currentUserId = GetCurrentUserIdOrThrow(); } catch (Exception ex) { return Unauthorized(ex.Message); }

            var s = await _db.ItRequestStatus.Include(x => x.Request).FirstOrDefaultAsync(x => x.StatusId == statusId);
            if (s == null) return NotFound("Status not found");
            var r = s.Request;

            if (r.UserId != currentUserId) return Unauthorized("You can only edit your own request");
            if (ShouldLock(r)) await LockOnlyAsync(r);
            if (r.IsLockedForEdit || (s.OverallStatus ?? "Draft") != "Draft") return StatusCode(423, "Edit window closed.");

            // --- Server-side guard: RequiredDate >= today + 7 ---
            var minDate = DateTime.Today.AddDays(7);
            if (dto.RequiredDate.Date < minDate)
                return BadRequest(new { message = $"RequiredDate must be at least {minDate:yyyy-MM-dd} or later." });

            // --- Cap shared perms to 6 ---
            if (dto.SharedPerms != null && dto.SharedPerms.Count > 6)
                dto.SharedPerms = dto.SharedPerms.Take(6).ToList();

            // --- Optional normalization of conflicting hardware ---
            bool hasDesktopAllIn = dto.Hardware?.Any(h => string.Equals(h.Category, "DesktopAllIn", StringComparison.OrdinalIgnoreCase)) == true;
            if (hasDesktopAllIn && dto.Hardware != null)
                dto.Hardware = dto.Hardware.Where(h => !string.Equals(h.Category, "NotebookAllIn", StringComparison.OrdinalIgnoreCase)
                                                    && !string.Equals(h.Category, "NotebookOnly", StringComparison.OrdinalIgnoreCase)).ToList();
            bool hasNotebookAllIn = dto.Hardware?.Any(h => string.Equals(h.Category, "NotebookAllIn", StringComparison.OrdinalIgnoreCase)) == true;
            if (hasNotebookAllIn && dto.Hardware != null)
                dto.Hardware = dto.Hardware.Where(h => !string.Equals(h.Category, "DesktopAllIn", StringComparison.OrdinalIgnoreCase)
                                                    && !string.Equals(h.Category, "DesktopOnly", StringComparison.OrdinalIgnoreCase)).ToList();

            // update simple fields
            r.Designation = dto.Designation;
            r.Location = dto.Location;
            r.EmploymentStatus = dto.EmploymentStatus;
            r.ContractEndDate = dto.ContractEndDate;
            r.RequiredDate = dto.RequiredDate;
            r.PhoneExt = dto.PhoneExt;

            // replace children
            var id = r.ItRequestId;

            _db.ItRequestHardwares.RemoveRange(_db.ItRequestHardwares.Where(x => x.ItRequestId == id));
            if (dto.Hardware != null)
                foreach (var x in dto.Hardware)
                    _db.ItRequestHardwares.Add(new ItRequestHardware { ItRequestId = id, Category = x.Category, Purpose = x.Purpose, Justification = x.Justification, OtherDescription = x.OtherDescription });

            _db.ItRequestEmails.RemoveRange(_db.ItRequestEmails.Where(x => x.ItRequestId == id));
            if (dto.Emails != null)
                foreach (var x in dto.Emails)
                    _db.ItRequestEmails.Add(new ItRequestEmail { ItRequestId = id, Purpose = null, ProposedAddress = x.ProposedAddress, Notes = null });

            _db.ItRequestOsRequirement.RemoveRange(_db.ItRequestOsRequirement.Where(x => x.ItRequestId == id));
            if (dto.OSReqs != null)
                foreach (var x in dto.OSReqs)
                    _db.ItRequestOsRequirement.Add(new ItRequestOsRequirement { ItRequestId = id, RequirementText = x.RequirementText });

            _db.ItRequestSoftware.RemoveRange(_db.ItRequestSoftware.Where(x => x.ItRequestId == id));
            if (dto.Software != null)
                foreach (var x in dto.Software)
                    _db.ItRequestSoftware.Add(new ItRequestSoftware { ItRequestId = id, Bucket = x.Bucket, Name = x.Name, OtherName = x.OtherName, Notes = x.Notes });

            _db.ItRequestSharedPermission.RemoveRange(_db.ItRequestSharedPermission.Where(x => x.ItRequestId == id));
            if (dto.SharedPerms != null)
                foreach (var x in dto.SharedPerms)
                    _db.ItRequestSharedPermission.Add(new ItRequestSharedPermission { ItRequestId = id, ShareName = x.ShareName, CanRead = x.CanRead, CanWrite = x.CanWrite, CanDelete = x.CanDelete, CanRemove = x.CanRemove });

            await _db.SaveChangesAsync();

            var remaining = r.EditableUntil.HasValue ? Math.Max(0, (int)(r.EditableUntil.Value - DateTime.UtcNow).TotalSeconds) : 0;
            return Ok(new { message = "Draft saved", remainingSeconds = remaining });
        }


        // POST: send early
        [HttpPost("sendNow/{statusId:int}")]
        public async Task<IActionResult> SendNow(int statusId)
        {
            int uid;
            try { uid = GetCurrentUserIdOrThrow(); }
            catch (Exception ex) { return Unauthorized(ex.Message); }

            var s = await _db.ItRequestStatus
                .Include(x => x.Request)
                .FirstOrDefaultAsync(x => x.StatusId == statusId);

            if (s == null) return NotFound("Status not found");
            if (s.Request.UserId != uid) return Unauthorized("Only owner can send");

            // Allow SendNow as long as it’s still a Draft (even if the edit window already locked it).
            if ((s.OverallStatus ?? "Draft") != "Draft")
                return BadRequest(new { message = "Already sent or not in Draft." });

            // Ensure locked, then make it visible to approvers.
            if (!s.Request.IsLockedForEdit) s.Request.IsLockedForEdit = true;
            s.OverallStatus = "Pending";

            await _db.SaveChangesAsync();

            return Ok(new { message = "Sent to approval", overallStatus = s.OverallStatus });
        }




        // --------------------------------------------------------------------
        // GET: /ItRequestAPI/request/{statusId}
        // Adds lazy-lock and returns full request details for Edit page
        // --------------------------------------------------------------------
        [HttpGet("request/{statusId}")]
        public async Task<IActionResult> GetRequestDetail(int statusId)
        {
            // auth
            var userIdStr = _userManager.GetUserId(User);
            if (string.IsNullOrEmpty(userIdStr) || !int.TryParse(userIdStr, out int currentUserId))
                return Unauthorized("Invalid user");

            // load status + children
            var status = await _db.ItRequestStatus
                .Include(s => s.Request).ThenInclude(r => r!.Hardware)
                .Include(s => s.Request).ThenInclude(r => r!.Emails)
                .Include(s => s.Request).ThenInclude(r => r!.OsRequirements)
                .Include(s => s.Request).ThenInclude(r => r!.Software)
                .Include(s => s.Request).ThenInclude(r => r!.SharedPermissions)
                .Include(s => s.Flow)
                .FirstOrDefaultAsync(s => s.StatusId == statusId);

            if (status == null) return NotFound("Request not found");

            // lazy lock if the edit window has expired
            async Task LazyLockIfExpired(ItRequestStatus s)
            {
                var r = s.Request;
                var expired = !r.IsLockedForEdit && r.EditableUntil.HasValue && DateTime.UtcNow > r.EditableUntil.Value;
                if (!expired) return;

                r.IsLockedForEdit = true;
                await _db.SaveChangesAsync();
            }
            await LazyLockIfExpired(status);

            // remaining seconds for countdown
            var remaining = status.Request.EditableUntil.HasValue
                ? Math.Max(0, (int)(status.Request.EditableUntil.Value - DateTime.UtcNow).TotalSeconds)
                : 0;

            // role + canApprove (unchanged)
            string role =
                status.Flow.HodUserId == currentUserId ? "HOD" :
                status.Flow.GroupItHodUserId == currentUserId ? "GIT_HOD" :
                status.Flow.FinHodUserId == currentUserId ? "FIN_HOD" :
                status.Flow.MgmtUserId == currentUserId ? "MGMT" :
                "VIEWER";

            string currentUserStatus = "N/A";
            bool canApprove = false;
            if (string.Equals(status.OverallStatus, "Cancelled", StringComparison.OrdinalIgnoreCase) ||
                string.Equals(status.OverallStatus ?? "Draft", "Draft", StringComparison.OrdinalIgnoreCase))
            {
                canApprove = false;
            }

            if (role == "HOD")
            {
                currentUserStatus = status.HodStatus ?? "Pending";
                canApprove = currentUserStatus == "Pending";
            }
            else if (role == "GIT_HOD")
            {
                currentUserStatus = status.GitHodStatus ?? "Pending";
                canApprove = currentUserStatus == "Pending" && status.HodStatus == "Approved";
            }
            else if (role == "FIN_HOD")
            {
                currentUserStatus = status.FinHodStatus ?? "Pending";
                canApprove = currentUserStatus == "Pending" &&
                             status.HodStatus == "Approved" &&
                             status.GitHodStatus == "Approved";
            }
            else if (role == "MGMT")
            {
                currentUserStatus = status.MgmtStatus ?? "Pending";
                canApprove = currentUserStatus == "Pending" &&
                             status.HodStatus == "Approved" &&
                             status.GitHodStatus == "Approved" &&
                             status.FinHodStatus == "Approved";
            }
            // No actions allowed on cancelled requests
            


            // RESPONSE: include full main fields for Edit page
            return Ok(new
            {
                // full request fields you need to prefill
                request = new
                {
                    status.Request.ItRequestId,
                    status.Request.StaffName,
                    status.Request.DepartmentName,
                    status.Request.CompanyName,
                    status.Request.Designation,
                    status.Request.Location,
                    status.Request.EmploymentStatus,
                    status.Request.ContractEndDate,
                    status.Request.RequiredDate,
                    status.Request.PhoneExt,
                    status.Request.SubmitDate
                },

                approverRole = role,

                hardware = status.Request.Hardware?.Select(h => new {
                    h.Id,
                    Category = h.Category ?? "",
                    Purpose = h.Purpose ?? "",
                    Justification = h.Justification ?? "",
                    OtherDescription = h.OtherDescription ?? ""
                }),

                emails = status.Request.Emails?.Select(e => new {
                    e.Id,
                    Purpose = e.Purpose ?? "",
                    ProposedAddress = e.ProposedAddress ?? "",
                    Notes = e.Notes ?? ""
                }),

                osreqs = status.Request.OsRequirements?.Select(o => new {
                    o.Id,
                    RequirementText = o.RequirementText ?? ""
                }),

                software = status.Request.Software?.Select(sw => new {
                    sw.Id,
                    Bucket = sw.Bucket ?? "",
                    Name = sw.Name ?? "",
                    OtherName = sw.OtherName ?? "",
                    Notes = sw.Notes ?? ""
                }),

                sharedPerms = status.Request.SharedPermissions?.Select(sp => new {
                    sp.Id,
                    ShareName = sp.ShareName ?? "",
                    CanRead = sp.CanRead,
                    CanWrite = sp.CanWrite,
                    CanDelete = sp.CanDelete,
                    CanRemove = sp.CanRemove
                }),

                status = new
                {
                    hodStatus = status.HodStatus ?? "Pending",
                    gitHodStatus = status.GitHodStatus ?? "Pending",
                    finHodStatus = status.FinHodStatus ?? "Pending",
                    mgmtStatus = status.MgmtStatus ?? "Pending",

                    hodSubmitDate = status.HodSubmitDate,
                    gitHodSubmitDate = status.GitHodSubmitDate,
                    finHodSubmitDate = status.FinHodSubmitDate,
                    mgmtSubmitDate = status.MgmtSubmitDate,

                    overallStatus = status.OverallStatus ?? "Pending",
                    canApprove = canApprove,
                    currentUserStatus = currentUserStatus
                },

                // edit window info for the Edit page
                edit = new
                {
                    overallStatus = status.OverallStatus ?? "Draft",
                    isEditable = !status.Request.IsLockedForEdit && (status.OverallStatus ?? "Draft") == "Draft" && remaining > 0,
                    remainingSeconds = remaining,
                    editableUntil = status.Request.EditableUntil
                }
            });
        }



        // --------------------------------------------------------------------
        // GET: /ItRequestAPI/lookups
        // --------------------------------------------------------------------
        [HttpGet("lookups")]
        public IActionResult Lookups()
        {
            var hardwareCategories = new[] {
                "DesktopAllIn","NotebookAllIn","DesktopOnly","NotebookOnly","NotebookBattery","PowerAdapter","Mouse","ExternalHDD","Other"
            };
            var hardwarePurposes = new[] { "NewRecruitment", "Replacement", "Additional" };
            var employmentStatuses = new[] { "Permanent", "Contract", "Temp", "New Staff" };
            var softwareBuckets = new[] { "General", "Utility", "Custom" };

            return Ok(new
            {
                hardwareCategories,
                hardwarePurposes,
                employmentStatuses,
                softwareBuckets
            });
        }

        
        // --------------------------------------------------------------------
        // POST: /ItRequestAPI/cancel
        public class CancelDto { public int RequestId { get; set; } public string? Reason { get; set; } }

        [HttpPost("cancel")]
        public async Task<IActionResult> CancelRequest([FromBody] CancelDto dto)
        {
            int userId;
            try { userId = GetCurrentUserIdOrThrow(); }
            catch (UnauthorizedAccessException ex) { return Unauthorized(ex.Message); }

            var req = await _db.ItRequests.FirstOrDefaultAsync(r => r.ItRequestId == dto.RequestId);
            if (req == null) return NotFound("Request not found");
            if (req.UserId != userId) return Unauthorized("You can only cancel your own requests");

            var s = await _db.ItRequestStatus.FirstOrDefaultAsync(x => x.ItRequestId == req.ItRequestId);
            if (s == null) return NotFound("Status row not found");

            var overall = s.OverallStatus ?? "Draft";

            // Allow cancel:
            // - Draft (always)
            // - Pending (only if ALLOW_PENDING_CANCEL == true) and no approvals have started
            bool allPending =
                (s.HodStatus ?? "Pending") == "Pending" &&
                (s.GitHodStatus ?? "Pending") == "Pending" &&
                (s.FinHodStatus ?? "Pending") == "Pending" &&
                (s.MgmtStatus ?? "Pending") == "Pending";

            bool canCancelNow =
                overall == "Draft" ||
                (ALLOW_PENDING_CANCEL && overall == "Pending" && allPending);

            if (!canCancelNow)
                return BadRequest(new { message = "Cannot cancel after approvals have started or once decided." });

            s.OverallStatus = "Cancelled";
            req.IsLockedForEdit = true;
            // TODO: persist dto.Reason if you add a column

            await _db.SaveChangesAsync();
            return Ok(new { message = "Request cancelled", requestId = req.ItRequestId, statusId = s.StatusId, overallStatus = s.OverallStatus });
        }


    }
}
