const fs = require('fs');

const sleep = (ms) => {
    return new Promise(resolve => {
        console.log(`${(ms / 1000).toFixed(2)}s SHOULD WAIT`);
        setTimeout(resolve, ms);
    })
}
function convertStringToJson(string) {
    try {
        return JSON.parse(string);
    } catch (error) {
        console.log(error);
        return {};
    }
}

const checkCondition = ({ data, condition }, conditionArray = new Set()) => {
    let ands = true;
    for (const key in condition) {
        const conditionValue = condition[key];
        if (key == 'or') {
            let ors = false;
            for (const cond of conditionValue) {
                // const orConditionChecking = checkCondition({ data, condition: cond }, conditionArray);
                // ors = ors || orConditionChecking.ok;
                ors = ors || checkCondition({ data, condition: cond });
                // conditionArray = new Set([...conditionArray, ...orConditionChecking.conditionArray])
                if (ors) break;
            }
            ands = ands && ors;
        } else {
            if (data[key] == 'true') data[key] = true;
            if (data[key] == 'false') data[key] = false;
            switch (key) {
                case [Op.gt]:
                    ands = ands && data[key] > conditionValue;
                    // if (!conditionArray.has(key)) conditionArray.add(key);
                    break;
                default:
                    ands = ands && data[key] == conditionValue;
                // if (!conditionArray.has(key)) conditionArray.add(key);
            }
        }
        if (!ands) {
            // return { ok: false, conditionArray };
            return false;
        }
    }

    // return { ok: true, conditionArray };
    return true;
};

function checkingOptions({ question, answer }) {
    let options = convertStringToJson(question?.options) || [],
        inputType = +question?.inputType || -1;

    // console.log("options", options)
    if (!Array.isArray(options)) options = [options];
    const OptionsValidValue = options.map((option) => {
        return parseValue(option?.value);
    })
    const questionAnswer = answer[question.id];
    if (questionAnswer === null || questionAnswer === undefined) return {
        ok: false
    }

    let min = question?.min,
        max = question?.max,
        length = question?.length,
        validation = question?.validation;

    switch (inputType) {
        case 1: //TEXT
        case 3:
            if (typeof questionAnswer !== "string") {
                return {
                    statusCode: 400,
                    message: ErrorMessages.InvalidData
                }
            }
            if (length && isNumber(length) && questionAnswer.length > +length) {
                return {
                    statusCode: 400,
                    message: ErrorMessages.InvalidTextLength(question.id, length)
                }
            }
            if (validation && isValidRegex(validation)) {
                let validationRegex = new RegExp(validation);
                if (!validationRegex.test(questionAnswer)) return {
                    statusCode: 400,
                    message: ErrorMessages.InvalidTextPattern
                }
            }

            break;
        case 2: //NUMBER
            if (!isNumber(questionAnswer)) {
                return {
                    statusCode: 400,
                    message: ErrorMessages.InvalidNumber(question.id)
                }
            }
            if (min && isNumber(min) && +min > +questionAnswer) return {
                statusCode: 400,
                message: ErrorMessages.InvalidMinNumber(question.id, min)
            }
            if (max && isNumber(max) && +max < +questionAnswer) return {
                statusCode: 400,
                message: ErrorMessages.InvalidMaxNumber(question.id, max)
            }
            break;
        case 4: // tak entekhab
        case 6:
            if (!OptionsValidValue.includes(questionAnswer)) {
                return {
                    statusCode: 400,
                    message: ErrorMessages.InvalidSelectedOption(question.id)
                }
            }
            break;
        case 5: // chand entekhab
        case 7:
            if (Array.isArray(questionAnswer)) {
                for (let ans of questionAnswer) {
                    if (!OptionsValidValue.includes(ans)) return { statusCode: 400, message: ErrorMessages.InvalidAdditionalData(question.id) };
                }
            } else if (typeof questionAnswer === "string") {
                for (let ans of questionAnswer.split(",")) {
                    if (!OptionsValidValue.includes(ans)) return { statusCode: 400, message: ErrorMessages.InvalidAdditionalData(question.id) };
                }
            } else {
                return { statusCode: 400, message: ErrorMessages.InvalidData(question.id) };
            }
            break;
        case 8:
            if (!isValidDateStrict(questionAnswer)) return { statusCode: 400, message: ErrorMessages.InvalidDate(question.id) };
            const questionDate = new Date(questionAnswer);
            if (min) {
                if (isValidDateStrict(min)) {
                    if (new Date(min) > questionDate) return {
                        statusCode: 400,
                        message: ErrorMessages.InvalidMinDate(question.id)
                    }
                } else if (min === "now()") {
                    if (new Date() > questionDate) return {
                        statusCode: 400,
                        message: ErrorMessages.InvalidMinDate(question.id)
                    }
                } else if (min.includes("day")) {
                    let days = min.match(/day(s)?\(([0-9]{1,2})\)/)[2];
                    if (
                        !isNaN(+days)
                        // &&
                        // Date.now() < questionDate
                        &&
                        Math.abs(Date.now() - questionDate.getTime()) > convertDaysToSeconds(days)
                    ) {
                        return {
                            statusCode: 400,
                            message: ErrorMessages.InvalidDayMinDate(question.id, days)
                        }
                    }
                }
            }
            if (max) {
                if (isValidDateStrict(max)) {
                    if (new Date(max) < questionDate) return {
                        statusCode: 400,
                        message: ErrorMessages.InvalidMaxDate(question.id)
                    }
                } else if (max === "now()") {
                    if (new Date() < questionDate) return {
                        statusCode: 400,
                        message: ErrorMessages.InvalidMaxDate(question.id)
                    }
                } else if (max.includes("day")) {
                    let days = max.match(/day(s)?\(([0-9]{1,2})\)/)[2];
                    if (
                        !isNaN(+days)
                        //     &&
                        // Date.now() < questionDate
                        &&
                        Math.abs(Date.now() - questionDate.getTime()) < convertDaysToSeconds(days)
                    ) {
                        return {
                            statusCode: 400,
                            message: ErrorMessages.InvalidDayMaxDate(question.id, days)
                        }
                    }
                }
            }
            break;
        case 10:
            if (!isBoolean(questionAnswer)) return { statusCode: 400, message: ErrorMessages.InvalidBooleanData(question.id) }
            break;
        default:
            return {
                statusCode: 400,
                message: ErrorMessages.InvalidInputType(question.id)
            }
    }
    return true;
}

async function checkingConditionTypes({ fQ, question, mojazData = [], answer }) {
    let check = false, checkOption = false, inputType = 10;
    let condition = {}, isRequired = null;
    let requiredCondition = {}, validation = null;

    requiredCondition = question?.requiredCondition || {};
    validation = question?.validation || null;

    condition = convertStringToJson(fQ?.condition) || convertStringToJson(question?.condition) || null;
    isRequired = returnBooleanOfArguments([fQ?.isRequired, question?.required]);

    inputType = question?.inputType || 10;

    console.log("question id:", question.id)
    console.log("requiredCondition", requiredCondition);
    // console.log("validation",validation);
    console.log("condition", condition);
    console.log("isRequired", isRequired);
    // console.log("options",options);
    console.log("inputType", inputType);

    //1.check requiredConditions
    //2.check Conditions and check they required or not
    //3.check Options and check they required or not
    //4.check validation
    //5.remove extra data

    //1.check requiredConditions
    check = checkCondition({ data: answer, condition: requiredCondition })
    if (check.ok === false) throw `${defects[question.id]}`;
    mojazData = [...new Set([...mojazData, ...check.conditionArray])];
    console.log("DONE SOLVE requiredConditions")

    //2.check Conditions and check they required or not
    check = checkCondition({ data: answer, condition: condition })
    checkOption = checkingOptions({ question, answer });
    if ((check.ok === false || checkOption.ok === false) && isRequired === true) throw `${defects[question.id]}`;
    if (checkOption.ok === true) mojazData.push(`${question.id}`);
    mojazData = [...new Set([...mojazData, ...check.conditionArray])];
    console.log("DONE SOLVE condition")

    //3.check Validation (ro bayad beporsam az aghaye sadeghi)

    console.log("mojazDataAfter-requiredCondition-condition", mojazData);

    return {
        mojazData
    }
}
function isBoolean(value) {
    if(typeof value==="string"){
      if(value?.trim()?.toLowerCase()=="true") value=true;
      else if(value?.trim()?.toLowerCase()=="false") value=false;
    }
    return typeof value === 'boolean';
}
function safeRegexFromString(regexString) {
    try {
      const parts = regexString.split('/');
      if (parts.length < 3) {
        // اگر / وجود نداشت، کل رشته به عنوان pattern در نظر گرفته می‌شود
        return new RegExp(regexString);
      }
      const pattern = parts.slice(1, -1).join('/'); // برای الگوهایی که شامل / هستند
      const flags = parts[parts.length - 1];
      return new RegExp(pattern, flags);
    } catch (error) {
      console.error("Invalid regex string:", error);
    //   return null;
      throw `invalidRegexFrom:${regexString}`;
    }
}
function returnBooleanOfArguments(array){
  if(!Array.isArray(array)) return false;
  for(let i of array){
      if(isBoolean(i)) return !!i;
  }
  return false;
}
function isNumber(value) {
  // return typeof value === 'number' && !isNaN(value) && isFinite(value);
  // return !isNaN(parseInt(value)) && isFinite(value);
  return !isNaN(+value) && isFinite(+value);
}
function parseValue(value) {
  // اگر ورودی از قبل رشته نباشد، همان مقدار را برگردانیم.
  if (typeof value !== 'string') {
    return value;
  }

  const trimmed = value.trim();

  // // بررسی مقادیر بولی
  // if (trimmed.toLowerCase() === 'true') return true;
  // if (trimmed.toLowerCase() === 'false') return false;

  // بررسی null و undefined
  if (trimmed.toLowerCase() === 'null') return null;
  if (trimmed.toLowerCase() === 'undefined') return undefined;

  // بررسی عدد
  // اگر رشته به صورت عددی تفسیر شود (توجه کنید که isNaN('') === false، پس باید رشته خالی هم چک شود)
  if (trimmed !== '' && isNumber(trimmed)) {
    return Number(trimmed);
  }

  // بررسی تاریخ
  // Date.parse در صورت عدم توانایی در تبدیل، مقدار NaN برمی‌گرداند.
  const timestamp = Date.parse(trimmed);
  if (!isNaN(timestamp)) {
    return new Date(trimmed);
  }

  // بررسی آرایه یا شیء (در صورتی که رشته با { یا [ شروع و به ترتیب با } یا ] پایان یابد)
  if (
    (trimmed.startsWith('{') && trimmed.endsWith('}')) ||
    (trimmed.startsWith('[') && trimmed.endsWith(']'))
  ) {
    try {
      return JSON.parse(trimmed);
    } catch (e) {
      // در صورت بروز خطا، به ادامه کد می‌رویم
    }
  }

  // در نهایت اگر هیچ یک از شرایط بالا صدق نکرد،
  // رشته اصلی را برمی‌گردانیم.
  return value;
}

function isValidDateStrict(date) {
  if (date instanceof Date && !isNaN(date.getTime())) {
      return true;
  }
  if (typeof date === "string") {
      const parsed = new Date(date);
      return parsed instanceof Date && !isNaN(parsed.getTime());
  }
  return false;
}

function convertDaysToSeconds(days){
  return days * 24 * 3600 * 1000;
}

function isValidRegex(str) {
  try {
      if (str.startsWith('/')) {
          let endSlashIndex = -1;
          let backslashCount = 0;
          
          // شروع جستجو از ایندکس ۱ (بعد از اسلش اول)
          for (let i = 1; i < str.length; i++) {
              if (str[i] === '\\') {
                  backslashCount++;
              } else {
                  if (str[i] === '/' && backslashCount % 2 === 0) {
                      endSlashIndex = i;
                      break;
                  }
                  backslashCount = 0;
              }
          }

          // اگر اسلش پایانی پیدا نشد
          if (endSlashIndex === -1) {
              return false;
          }

          const pattern = str.slice(1, endSlashIndex);
          const modifiers = str.slice(endSlashIndex + 1);
          
          new RegExp(pattern, modifiers);
      } else {
          new RegExp(str);
      }
      return true;
  } catch (e) {
      return false;
  }
}

async function saveLargeObject(obj, filename) {
  const ws = fs.createWriteStream(filename, { encoding: 'utf8' });
  ws.write('{\n');

  const entries = Object.entries(obj);
  for (let i = 0; i < entries.length; i++) {
    const [key, value] = entries[i];
    // stringify مقدار
    const chunk = JSON.stringify(value, null, 2)
      .split('\n')
      .map((line, idx) => idx === 0
        ? `  "${key}": ${line}`
        : `     ${line}`)
      .join('\n');
    // اگر اولین نیست، قبلش کاما بگذار
    ws.write((i > 0 ? ',\n' : '') + chunk);
  }

  ws.write('\n}\n');
  ws.end();

  await new Promise((res, rej) => {
    ws.on('finish', res);
    ws.on('error', rej);
  });
  console.log(`فایل ${filename} ذخیره شد.`);
}


module.exports = {
    sleep,
    convertStringToJson,
    isValidRegex,
    convertDaysToSeconds,
    isValidDateStrict,
    parseValue,
    isNumber,
    returnBooleanOfArguments,
    safeRegexFromString,
    isBoolean,
    checkingConditionTypes,
    checkingOptions,
    checkCondition,
    saveLargeObject
}