using GB5Shared.DaprCache;
using GB5Shared.DTO.Framework.AutoNumber;
using GB5Shared.DTO.Framework.Criteria;
using GB5Shared.DTO.Framework.Login;
using GB5Shared.GenerateAutoNumber;
using GB5Shared.Resource.Response;
using GB5Shared.Validation;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Threading;
using System.Threading.Tasks;
using TMSDAL.CustomCode.AssesmentQuestion;
using TMSDAL.DTO.AssessmentQuestion;
using static GB5Shared.GB5Constant.Constant;
namespace TMSBLL.AssessmentQuestion
{
///
/// BLL for MASSESSMENTQUESTION.
/// Handles save, update, delete and retrieval of assessment questions
/// that belong to a MSESSIONASSESSMENT (Simple mode only).
///
/// FIXES FROM ORIGINAL (copy-paste from TrainerBLL — never updated):
/// [1] All Trainer field references removed (TrainerCode, TrainerFullName,
/// TrainerType, EmployeeId, ExternalVendorId, TrainerId) — wrong entity.
/// [2] Validation rewritten for AssessmentQuestion fields.
/// [3] AutoNumber key changed from "AssesmentQuestion" → "ASSESSMENTQUESTION"
/// (consistent uppercase convention used across the codebase).
/// [4] isNew declared ABOVE try so catch block can access it for rollback.
/// [5] Rollback condition fixed: fires on autoNumberDTO != null only —
/// removed wrong "dto.AssessmentQuestionId > 0" check.
/// [6] ValidationException now re-thrown directly (not wrapped in Exception).
/// [7] Cache key uses AssessmentQuestionId not TrainerId.
/// [8] Empty try/catch { throw; } wrappers replaced with direct awaits.
/// [9] CancellationToken added to GET methods for consistency.
/// [10] Using directives cleaned up (removed unused StringBuilder, List, Linq).
///
public class AssessmentQuestionBLL : IAssessmentQuestionBLL
{
private readonly IAssessmentQuestionDAL _AssessmentQuestionDAL;
private readonly AutoNumber _AutoNumber;
private readonly IValidation _Validation;
private readonly KeyInvalidate _KeyInvalidate;
public AssessmentQuestionBLL(
IAssessmentQuestionDAL assessmentQuestionDAL,
AutoNumber autoNumber,
IValidation validation,
KeyInvalidate keyInvalidate)
{
_AssessmentQuestionDAL = assessmentQuestionDAL;
_AutoNumber = autoNumber;
_Validation = validation;
_KeyInvalidate = keyInvalidate;
}
// ── GET single ────────────────────────────────────────────────────────
// [FIX-8] Empty try/catch { throw; } removed — no value, extra stack frame
public Task GetAssessmentQuestion(int assessmentQuestionId, LoginDTO loginDTO)
=> _AssessmentQuestionDAL.GetAssesmentQuestion(assessmentQuestionId, loginDTO);
// ── GET list / picklist ───────────────────────────────────────────────
public Task GetSelectList(CriteriaDTO criteriaDTO, LoginDTO loginDTO, CancellationToken ct)
=> _AssessmentQuestionDAL.GetSelectList(criteriaDTO, loginDTO, ct);
public Task GetSelectListAssessmentQuestion(LoginDTO loginDTO)
=> _AssessmentQuestionDAL.GetSelectListAssesmentQuestion(loginDTO);
// ── SAVE / UPDATE ─────────────────────────────────────────────────────
///
/// Inserts or updates an AssessmentQuestion record.
///
/// Insert: AssessmentQuestionId == 0
/// Update: AssessmentQuestionId > 0
///
/// Validation rules:
/// - AssessmentId required (> 0)
/// - QuestionText required (not empty)
/// - CorrectAnswer required (not empty)
/// - QuestionType must be 0–5
/// - Marks must be > 0
/// - OptionsJson required for types that need options (MCQ=0, MultiSelect=3, Matching=4, Ordering=5)
///
public async Task SaveAssessmentQuestion(AssessmentQuestionDTO dto, LoginDTO loginDTO)
{
if (dto == null) throw new ArgumentNullException(nameof(dto));
// [FIX-4] Declared ABOVE try — catch block must read isNew for rollback
bool isNew = dto.AssessmentQuestionId == 0;
AutoNumberDTO? autoNumberDTO = null;
try
{
// ── VALIDATION ────────────────────────────────────────────────
// AssessmentId — must reference a valid parent assessment
if (dto.AssessmentId == 0)
throw new ValidationException("AssessmentId is required.");
// QuestionText — mandatory
await _Validation.NotEmpty(dto.QuestionText, nameof(dto.QuestionText));
// CorrectAnswer — mandatory
await _Validation.NotEmpty(dto.CorrectAnswer, nameof(dto.CorrectAnswer));
// QuestionType — CHECK constraint IN (0,1,2,3,4,5)
if (dto.QuestionType > 5)
throw new ValidationException("QuestionType must be 0 (MCQ), 1 (TrueFalse), 2 (ShortAnswer), 3 (MultiSelect), 4 (Matching) or 5 (Ordering).");
// Marks — must be positive
if (dto.Marks <= 0)
throw new ValidationException("Marks must be greater than 0.");
// OptionsJson — required for question types that need answer options
bool requiresOptions = dto.QuestionType == 0 // MCQ
|| dto.QuestionType == 3 // MultiSelect
|| dto.QuestionType == 4 // Matching
|| dto.QuestionType == 5; // Ordering
if (requiresOptions && string.IsNullOrWhiteSpace(dto.OptionsJson))
throw new ValidationException(
$"OptionsJson is required for QuestionType {dto.QuestionType} " +
"(MCQ, MultiSelect, Matching, Ordering).");
// SlNo — default to 1 if not provided
if (dto.SlNo <= 0)
dto.SlNo = 1;
// ── ID GENERATION + SAVE ──────────────────────────────────────
string message;
if (isNew)
{
// [FIX-3] Uppercase key consistent with codebase convention
autoNumberDTO = await _AutoNumber.GetAutoNumber(1, "ASSESSMENTQUESTION", loginDTO);
dto.AssessmentQuestionId = autoNumberDTO.StartNumber;
await _AssessmentQuestionDAL.SaveAssesmentQuestion(dto, loginDTO);
message = string.Format(SuccessResponse.SaveSuccessWithName, dto.QuestionText);
}
else
{
await _AssessmentQuestionDAL.UpdateAssesmentQuestion(dto, loginDTO);
message = string.Format(SuccessResponse.UpdateSuccessWithName, dto.QuestionText);
}
// ── CACHE INVALIDATION ────────────────────────────────────────
// [FIX-7] Cache key uses AssessmentQuestionId — not TrainerId
var cacheKey = new CacheKeyGeneration().KeyGeneration(
dto.AssessmentQuestionId,
EntityConstant.OBJECTASSESMENTQUESTION,
CacheKeyLevel.CLIENT_LEVEL,
loginDTO);
await _KeyInvalidate.AllInvalidateCache(cacheKey);
return message;
}
catch (ValidationException)
{
// [FIX-6] Re-throw directly — don't wrap in Exception (loses type info)
throw;
}
catch
{
// [FIX-5] Rollback whenever AutoNumber block was reserved.
// Removed wrong "dto.AssessmentQuestionId > 0" condition —
// always true after ID assignment, would suppress rollback.
if (autoNumberDTO != null)
await _AutoNumber.RollbackAutoNumber("ASSESSMENTQUESTION", autoNumberDTO.StartNumber, loginDTO);
throw;
}
}
// ── DELETE ────────────────────────────────────────────────────────────
///
/// Soft-deletes an AssessmentQuestion and invalidates its cache entry.
///
public async Task DeleteAssessmentQuestion(int assessmentQuestionId, LoginDTO loginDTO)
{
// [FIX-8] Empty try/catch { throw; } removed
var result = await _AssessmentQuestionDAL.DeleteAssesmentQuestion(assessmentQuestionId, loginDTO);
// [FIX-7] Cache key uses assessmentQuestionId — not trainerId
var cacheKey = new CacheKeyGeneration().KeyGeneration(
assessmentQuestionId,
EntityConstant.OBJECTASSESMENTQUESTION,
CacheKeyLevel.CLIENT_LEVEL,
loginDTO);
await _KeyInvalidate.AllInvalidateCache(cacheKey);
return result;
}
}
}