import { psadtValidationRules } from '../data/psadtValidationRules.js';

const ruleById = new Map(psadtValidationRules.map((rule) => [rule.id, rule]));
const legacyFunctions = [
  'Execute-Process',
  'Execute-MSI',
  'Execute-MSP',
  'Show-InstallationWelcome',
  'Show-InstallationPrompt',
  'Show-InstallationProgress',
  'Show-InstallationRestartPrompt',
  'Write-Log',
  'Remove-MSIApplications',
  'Invoke-HKCURegistrySettingsForAllUsers'
];

export function validationRuleCatalog() {
  return psadtValidationRules;
}

export function validatePsadtScript(content = '', context = {}) {
  const text = String(content || '');
  const findings = [
    ...checkPowerShellRequirement(text),
    ...checkRequireAdminConfig(text),
    ...checkRemovedDetectionConfig(text),
    ...checkAppProcessesToClose(text),
    ...checkSessionCleanup(text),
    ...checkModuleVersionDrift(text),
    ...checkDeployModeAuto(text),
    ...checkLegacyFunctions(text),
    ...checkLegacyDeploymentNaming(text),
    ...checkLegacyDeploymentVariables(text),
    ...checkEnvironmentVariables(text),
    ...checkServiceUi(text),
    ...checkDeferRunInterval(text, context),
    ...checkPromptIcon(text),
    ...checkRawMsiExec(text),
    ...checkStartProcessWait(text),
    ...checkExitCodes(text),
    ...checkGlobalModuleImport(text),
    ...checkDeprecatedObjectProperty(text),
    ...checkUserContextRequireAdmin(text, context)
  ].map((finding, index) => ({
    id: `${finding.ruleId}-${index + 1}`,
    ...ruleById.get(finding.ruleId),
    ...finding
  }));

  return {
    ok: findings.every((finding) => !['critical', 'error'].includes(finding.severity)),
    score: scoreFindings(findings),
    counts: countFindings(findings),
    findings,
    rules: psadtValidationRules,
    analyzedAt: new Date().toISOString()
  };
}

function add(ruleId, text, match, extra = {}) {
  return {
    ruleId,
    line: lineNumber(text, match.index ?? 0),
    excerpt: lineText(text, match.index ?? 0),
    ...extra
  };
}

function checkPowerShellRequirement(text) {
  const findings = [];
  for (const match of text.matchAll(/#requires\s+-version\s+([0-9.]+)/gi)) {
    const version = Number.parseFloat(match[1]);
    if (Number.isFinite(version) && version < 5.1) findings.push(add('REQ-PSVERSION-MINIMUM', text, match, { detected: match[0] }));
  }
  return findings;
}

function checkRequireAdminConfig(text) {
  const findings = [];
  const configPattern = /(Toolkit\.)?RequireAdmin\s*=/gi;
  for (const match of text.matchAll(configPattern)) {
    const nearby = text.slice(Math.max(0, match.index - 80), match.index + 160);
    if (!/\$adtSession\s*=|@\{|RequireAdmin\s*=\s*\$(true|false)/i.test(nearby) || /Toolkit\.RequireAdmin/i.test(match[0])) {
      findings.push(add('UPG-REQUIREADMIN-CONFIG', text, match));
    }
  }
  return findings;
}

function checkRemovedDetectionConfig(text) {
  return [...text.matchAll(/\b(Toolkit\.)?(OOBEDetection|SessionDetection)\b/gi)]
    .map((match) => add('UPG-REMOVED-CONFIG-DETECTION', text, match, { detected: match[0] }));
}

function checkAppProcessesToClose(text) {
  if (/AppProcessesToClose/i.test(text)) return [];
  return [...text.matchAll(/Show-ADTInstallationWelcome[^\n\r]*-CloseProcesses\s+(?!\$adtSession\.AppProcessesToClose)/gi)]
    .map((match) => add('UPG-APP-PROCESSES-SESSION', text, match));
}

function checkSessionCleanup(text) {
  if (!/Open-ADTSession/i.test(text)) return [];
  if (/Remove-ADTHashtableNullOrEmptyValues/i.test(text)) return [];
  const match = /Open-ADTSession/i.exec(text);
  return match ? [add('UPG-MISSING-SESSION-CLEANUP', text, match)] : [];
}

function checkLegacyFunctions(text) {
  const pattern = new RegExp(`\\b(${legacyFunctions.map(escapeRegExp).join('|')})\\b`, 'gi');
  return [...text.matchAll(pattern)]
    .filter((match) => !isCommentMatch(text, match))
    .map((match) => add('LEGACY-V3-FUNCTION', text, match, { detected: match[0] }));
}

function checkLegacyDeploymentNaming(text) {
  return [...text.matchAll(/\bDeploy-Application\.(ps1|exe)\b/gi)]
    .filter((match) => !isCommentMatch(text, match))
    .map((match) => add('LEGACY-DEPLOY-APPLICATION', text, match));
}

function checkLegacyDeploymentVariables(text) {
  return [...text.matchAll(/\$(appName|appVersion|appVendor|appArch|appLang|appRevision)\b/g)]
    .map((match) => add('LEGACY-DEPLOYMENT-VARIABLE', text, match, { detected: match[0] }));
}

function checkEnvironmentVariables(text) {
  if (/Open-ADTSession|Export-ADTEnvironmentTableToSessionState/i.test(text)) return [];
  return [...text.matchAll(/\$(envProgramFiles|envProgramFilesX86|envSystemRoot|envWinDir|envTemp)\b/g)]
    .map((match) => add('FAQ-ENV-VAR-BEFORE-SESSION', text, match, { detected: match[0] }));
}

function checkServiceUi(text) {
  return [...text.matchAll(/\b(ServiceUI\.exe|Invoke-ServiceUI\.ps1)\b/gi)]
    .filter((match) => !isCommentMatch(text, match))
    .map((match) => add('INTUNE-SERVICEUI', text, match));
}

function checkDeferRunInterval(text, context) {
  const findings = [];
  const intuneHint = context.platform === 'intune' || /Intune|IME|Microsoft Intune/i.test(text);
  for (const match of text.matchAll(/Show-ADTInstallationWelcome[^\n\r]*-AllowDefer\b(?![^\n\r]*-DeferRunInterval)/gi)) {
    if (intuneHint || /Invoke-AppDeployToolkit\.exe|DeployMode/i.test(text)) findings.push(add('INTUNE-DEFER-RUN-INTERVAL', text, match));
  }
  return findings;
}

function checkPromptIcon(text) {
  return [...text.matchAll(/Show-ADTInstallationPrompt[^\n\r]*\s-Icon\b/gi)]
    .filter((match) => !isCommentMatch(text, match))
    .map((match) => add('UI-PROMPT-ICON-FLUENT', text, match));
}

function checkRawMsiExec(text) {
  return [...text.matchAll(/\bmsiexec(?:\.exe)?\b[^\n\r]*(\/i|\/x|\/f|\/p)/gi)]
    .filter((match) => !isCommentMatch(text, match))
    .map((match) => add('INSTALL-RAW-MSIEXEC', text, match));
}

function checkStartProcessWait(text) {
  return [...text.matchAll(/Start-ADTProcess[^\n\r]*-FilePath\s+['"][^'"]+\.(exe|cmd|bat)['"][^\n\r]*/gi)]
    .filter((match) => !/-WaitForChildProcesses\b/i.test(match[0]) && !/-NoWait\b/i.test(match[0]))
    .map((match) => add('INSTALL-PROCESS-NO-WAIT-CHILD', text, match));
}

function checkExitCodes(text) {
  const findings = [];
  for (const match of text.matchAll(/\b(6[0-9]{4}|7[0-9]{4})\b/g)) {
    if (isCommentMatch(text, match)) continue;
    const code = Number(match[1]);
    if (code >= 60000 && code <= 68999) findings.push(add('EXIT-RESERVED-BUILTIN', text, match, { detected: String(code) }));
    if (code >= 69000 && code <= 69999) findings.push(add('EXIT-CUSTOM-RANGE', text, match, { detected: String(code) }));
  }
  return findings;
}

function checkModuleVersionDrift(text) {
  const findings = [];
  for (const match of text.matchAll(/ModuleVersion\s*=\s*['"]([0-9.]+)['"]/gi)) {
    if (isCommentMatch(text, match)) continue;
    if (compareVersion(match[1], '4.2.0') < 0) findings.push(add('REPO-MODULE-VERSION-DRIFT', text, match, { detected: match[1] }));
  }
  return findings;
}

function checkDeployModeAuto(text) {
  return [...text.matchAll(/-DeployMode\s+(Interactive|Silent|NonInteractive)\b/gi)]
    .filter((match) => !isCommentMatch(text, match))
    .map((match) => add('REPO-DEPLOYMODE-NO-AUTO', text, match, { detected: match[1] }));
}

function checkDeprecatedObjectProperty(text) {
  return [...text.matchAll(/\bGet-ADTObjectProperty\b/gi)]
    .filter((match) => !isCommentMatch(text, match))
    .map((match) => add('DEPRECATED-GET-ADTOBJECTPROPERTY', text, match));
}

function checkGlobalModuleImport(text) {
  return [...text.matchAll(/Import-Module\s+['"]?PSAppDeployToolkit['"]?(?![\\/])/gi)].map((match) => add('FAQ-GLOBAL-MODULE-IMPORT', text, match));
}

function checkUserContextRequireAdmin(text, context) {
  if (!/Start-ADTProcessAsUser|Start-ADTMsiProcessAsUser/i.test(text)) return [];
  if (/RequireAdmin\s*=\s*\$false/i.test(text) || context.requireAdmin === false) return [];
  const match = /Start-ADTMsiProcessAsUser|Start-ADTProcessAsUser/i.exec(text);
  return match ? [add('USERCONTEXT-REQUIREADMIN', text, match)] : [];
}

function scoreFindings(findings) {
  const penalties = { critical: 35, error: 22, warning: 10, info: 3 };
  const penalty = findings.reduce((total, finding) => total + (penalties[finding.severity] || 0), 0);
  return Math.max(0, 100 - penalty);
}

function countFindings(findings) {
  return findings.reduce((counts, finding) => {
    counts[finding.severity] = (counts[finding.severity] || 0) + 1;
    counts.total += 1;
    return counts;
  }, { total: 0, critical: 0, error: 0, warning: 0, info: 0 });
}

function lineNumber(text, index) {
  return text.slice(0, index).split(/\r?\n/).length;
}

function lineText(text, index) {
  const lines = text.split(/\r?\n/);
  return lines[lineNumber(text, index) - 1]?.trim().slice(0, 240) || '';
}

function escapeRegExp(value) {
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

function isCommentMatch(text, match) {
  return lineText(text, match.index ?? 0).trimStart().startsWith('#');
}

function compareVersion(left, right) {
  const a = String(left).split('.').map((part) => Number(part) || 0);
  const b = String(right).split('.').map((part) => Number(part) || 0);
  for (let index = 0; index < Math.max(a.length, b.length); index += 1) {
    if ((a[index] || 0) < (b[index] || 0)) return -1;
    if ((a[index] || 0) > (b[index] || 0)) return 1;
  }
  return 0;
}
