#!/bin/bash
# Don't use set -e globally — individual checks handle their own errors
cd "$(dirname "$0")/.."

echo "============================================="
echo "  avo-inspector QA Suite"
echo "============================================="
echo ""

PASS=0
FAIL=0
RESULTS=""

record() {
  local name="$1" status="$2" detail="$3"
  if [ "$status" = "PASS" ]; then
    PASS=$((PASS + 1))
    RESULTS="${RESULTS}\n  ✅ ${name}${detail:+ — $detail}"
  else
    FAIL=$((FAIL + 1))
    RESULTS="${RESULTS}\n  ❌ ${name}${detail:+ — $detail}"
  fi
}

# ─── Step 0: Clean build + pack ───────────────────────────────────
echo "▸ Step 0: Clean build + pack"
yarn build > /dev/null 2>&1
PACK_OUT=$(npm pack 2>&1)
TARBALL=$(ls -t avo-inspector-*.tgz | head -1)
TARBALL_PATH="$(pwd)/$TARBALL"
echo "  Packed: $TARBALL"
echo ""

# ─── Step 1: Full build regression ───────────────────────────────
echo "▸ Step 1: Full build regression (existing example project)"
pushd examples/ts-avo-inspector-example > /dev/null
rm -rf node_modules
yarn install --no-lockfile > /dev/null 2>&1
if yarn test --watchAll=false > /dev/null 2>&1; then
  record "Full build: example project tests" "PASS"
else
  record "Full build: example project tests" "FAIL"
fi
popd > /dev/null
echo ""

# ─── Step 2: Lite functional QA ──────────────────────────────────
echo "▸ Step 2: Lite functional QA"
QA_DIR=$(mktemp -d)
mkdir -p "$QA_DIR"

# package.json for the QA project
cat > "$QA_DIR/package.json" <<'PKGJSON'
{
  "private": true,
  "dependencies": {}
}
PKGJSON

# Install from tarball
cd "$QA_DIR"
npm install "$TARBALL_PATH" --save > /dev/null 2>&1

# 2a: Lite import resolves in Node.js
cat > "$QA_DIR/test-lite-require.js" <<'TESTJS'
try {
  const { AvoInspector, AvoInspectorEnv } = require("avo-inspector/lite");
  if (!AvoInspector || !AvoInspectorEnv) {
    console.log("FAIL: exports missing");
    process.exit(1);
  }
  console.log("PASS: lite require resolves");
} catch (e) {
  console.log("FAIL: " + e.message);
  process.exit(1);
}
TESTJS
if node "$QA_DIR/test-lite-require.js" 2>/dev/null; then
  record "Lite: require('avo-inspector/lite') resolves" "PASS"
else
  record "Lite: require('avo-inspector/lite') resolves" "FAIL"
fi

# 2b: Full import still works
# Note: full build is a webpack UMD bundle that uses `self` — needs browser-like global
cat > "$QA_DIR/test-full-require.js" <<'TESTJS'
global.self = global;
try {
  const { AvoInspector, AvoInspectorEnv } = require("avo-inspector");
  if (!AvoInspector || !AvoInspectorEnv) {
    console.log("FAIL: exports missing");
    process.exit(1);
  }
  console.log("PASS: full require resolves");
} catch (e) {
  console.log("FAIL: " + e.message);
  process.exit(1);
}
TESTJS
if node "$QA_DIR/test-full-require.js" 2>/dev/null; then
  record "Full: require('avo-inspector') resolves" "PASS"
else
  record "Full: require('avo-inspector') resolves" "FAIL"
fi

# 2c: Lite constructor works (prod, dev, staging)
cat > "$QA_DIR/test-lite-constructor.js" <<'TESTJS'
const { AvoInspector, AvoInspectorEnv } = require("avo-inspector/lite");

// Suppress XMLHttpRequest errors in Node
global.XMLHttpRequest = class { open() {} send() {} };

const envs = ["prod", "staging", "dev"];
let allOk = true;
for (const env of envs) {
  try {
    const inspector = new AvoInspector({
      apiKey: "qa-key",
      env: env,
      version: "1.0.0",
    });
    if (!inspector.apiKey) {
      console.log("FAIL: " + env + " constructor returned invalid instance");
      allOk = false;
    }
  } catch (e) {
    console.log("FAIL: " + env + " — " + e.message);
    allOk = false;
  }
}
if (allOk) {
  console.log("PASS: constructor works for all envs");
} else {
  process.exit(1);
}
TESTJS
if node "$QA_DIR/test-lite-constructor.js" 2>/dev/null; then
  record "Lite: constructor works (prod, dev, staging)" "PASS"
else
  record "Lite: constructor works (prod, dev, staging)" "FAIL"
fi

# 2d: trackSchemaFromEvent returns correct schema
cat > "$QA_DIR/test-lite-track.js" <<'TESTJS'
const { AvoInspector, AvoInspectorEnv } = require("avo-inspector/lite");

global.XMLHttpRequest = class { open() {} send() {} };
global.localStorage = { getItem: () => null, setItem: () => {}, removeItem: () => {} };

async function test() {
  const inspector = new AvoInspector({
    apiKey: "qa-key",
    env: AvoInspectorEnv.Prod,
    version: "1.0.0",
  });

  const schema = await inspector.trackSchemaFromEvent("QA Event", {
    name: "test",
    count: 42,
    active: true,
    tags: ["a", "b"],
  });

  if (!Array.isArray(schema) || schema.length !== 4) {
    console.log("FAIL: expected 4 properties, got " + JSON.stringify(schema));
    process.exit(1);
  }

  const types = schema.map(s => s.propertyType).sort();
  const expected = ["boolean", "int", "list", "string"];
  if (JSON.stringify(types) !== JSON.stringify(expected)) {
    console.log("FAIL: types mismatch — got " + JSON.stringify(types));
    process.exit(1);
  }

  // Verify no encryptedPropertyValue
  for (const prop of schema) {
    if (prop.encryptedPropertyValue) {
      console.log("FAIL: encryptedPropertyValue found on " + prop.propertyName);
      process.exit(1);
    }
  }

  console.log("PASS: trackSchemaFromEvent returns correct schema");
}
test().catch(e => { console.log("FAIL: " + e.message); process.exit(1); });
TESTJS
if node "$QA_DIR/test-lite-track.js" 2>/dev/null; then
  record "Lite: trackSchemaFromEvent returns correct schema" "PASS"
else
  record "Lite: trackSchemaFromEvent returns correct schema" "FAIL"
fi

# 2e: No dev code in lite dist
LITE_DIR="$QA_DIR/node_modules/avo-inspector/dist/lite"
if grep -rq "AvoEncryption\|noble\|EventSpecCache\|safe-regex\|encryptValue" "$LITE_DIR/" 2>/dev/null; then
  record "Lite: no dev code in dist/lite/" "FAIL" "found encryption/eventSpec references"
else
  record "Lite: no dev code in dist/lite/" "PASS"
fi

# 2f: Full dist still has dev code (not stripped)
FULL_DIR="$QA_DIR/node_modules/avo-inspector/dist"
if grep -rq "AvoEncryption\|EventSpecCache" "$FULL_DIR/AvoInspector.js" 2>/dev/null; then
  record "Full: dist/ still contains dev code (unchanged)" "PASS"
else
  record "Full: dist/ still contains dev code (unchanged)" "FAIL" "dev code missing from full build"
fi

cd "$(dirname "$0")/.."
rm -rf "$QA_DIR"
echo ""

# ─── Step 3: TypeScript QA ───────────────────────────────────────
echo "▸ Step 3: TypeScript — publicEncryptionKey rejected by lite"
TS_DIR=$(mktemp -d)
cat > "$TS_DIR/package.json" <<PKGJSON
{ "private": true }
PKGJSON
cd "$TS_DIR"
npm install "$TARBALL_PATH" typescript@latest --save > /dev/null 2>&1

# Test should-fail: publicEncryptionKey not accepted
cat > "$TS_DIR/tsconfig.fail.json" <<TSCONF
{
  "compilerOptions": {
    "module": "node16",
    "target": "ES2020",
    "strict": true,
    "moduleResolution": "node16",
    "noEmit": true
  },
  "include": ["should-fail.ts"]
}
TSCONF

cat > "$TS_DIR/should-fail.ts" <<'TESTTS'
import { AvoInspector, AvoInspectorEnv } from "avo-inspector/lite";
const inspector = new AvoInspector({
  apiKey: "test",
  env: AvoInspectorEnv.Prod,
  version: "1.0.0",
  publicEncryptionKey: "some-key",
});
TESTTS

# Test should-pass: valid lite usage
cat > "$TS_DIR/tsconfig.pass.json" <<TSCONF
{
  "compilerOptions": {
    "module": "node16",
    "target": "ES2020",
    "strict": true,
    "moduleResolution": "node16",
    "noEmit": true
  },
  "include": ["should-pass.ts"]
}
TSCONF

cat > "$TS_DIR/should-pass.ts" <<'TESTTS'
import { AvoInspector, AvoInspectorEnv } from "avo-inspector/lite";
const inspector = new AvoInspector({
  apiKey: "test",
  env: AvoInspectorEnv.Prod,
  version: "1.0.0",
});
TESTTS

(cd "$TS_DIR" && npx tsc --project tsconfig.fail.json > /dev/null 2>&1)
TS_FAIL_EXIT=$?
if [ "$TS_FAIL_EXIT" -ne 0 ]; then
  record "TS: publicEncryptionKey causes compile error" "PASS"
else
  record "TS: publicEncryptionKey causes compile error" "FAIL" "compiled without error"
fi

(cd "$TS_DIR" && npx tsc --project tsconfig.pass.json > /dev/null 2>&1)
TS_PASS_EXIT=$?
if [ "$TS_PASS_EXIT" -eq 0 ]; then
  record "TS: lite import without publicEncryptionKey compiles" "PASS"
else
  record "TS: lite import without publicEncryptionKey compiles" "FAIL"
fi

cd "$(dirname "$0")/.."
rm -rf "$TS_DIR"
echo ""

# ─── Step 4: Bundle size QA ──────────────────────────────────────
echo "▸ Step 4: Bundle size verification"

SIZE_DIR=$(mktemp -d)
cat > "$SIZE_DIR/package.json" <<PKGJSON
{ "private": true }
PKGJSON
cd "$SIZE_DIR"
npm install "$TARBALL_PATH" esbuild terser --save --loglevel=error 2>&1

# 4a: Lite bundle with esbuild + terser (simulates GTM pipeline)
cat > "$SIZE_DIR/entry-lite.js" <<'ENTRYJS'
var mod = require("avo-inspector/lite");
var inspector = new mod.AvoInspector({ apiKey: "k", env: mod.AvoInspectorEnv.Prod, version: "1" });
inspector.trackSchemaFromEvent("e", { p: "v" });
ENTRYJS

cat > "$SIZE_DIR/entry-full.js" <<'ENTRYJS'
var mod = require("avo-inspector");
var inspector = new mod.AvoInspector({ apiKey: "k", env: mod.AvoInspectorEnv.Prod, version: "1" });
inspector.trackSchemaFromEvent("e", { p: "v" });
ENTRYJS

mkdir -p "$SIZE_DIR/build"

# Build lite
npx --yes esbuild "$SIZE_DIR/entry-lite.js" --bundle --platform=browser --outfile="$SIZE_DIR/build/lite.js" 2>&1 || echo "  esbuild lite failed"
npx --yes terser "$SIZE_DIR/build/lite.js" --compress passes=2,unsafe=true,unsafe_comps=true --mangle --output "$SIZE_DIR/build/lite.min.js" 2>&1 || echo "  terser lite failed"

# Build full
npx --yes esbuild "$SIZE_DIR/entry-full.js" --bundle --platform=browser --outfile="$SIZE_DIR/build/full.js" 2>&1 || echo "  esbuild full failed"
npx --yes terser "$SIZE_DIR/build/full.js" --compress passes=2,unsafe=true,unsafe_comps=true --mangle --output "$SIZE_DIR/build/full.min.js" 2>&1 || echo "  terser full failed"

if [ ! -f "$SIZE_DIR/build/lite.min.js" ] || [ ! -f "$SIZE_DIR/build/full.min.js" ]; then
  record "Size: lite < 7 KB gzipped" "FAIL" "build failed"
  record "Size: no dev code in minified lite bundle" "FAIL" "build failed"
  cd "$(dirname "$0")/.."
  rm -rf "$SIZE_DIR"
  echo ""
else

LITE_RAW=$(wc -c < "$SIZE_DIR/build/lite.js" | tr -d ' ')
LITE_MIN=$(wc -c < "$SIZE_DIR/build/lite.min.js" | tr -d ' ')
LITE_GZ=$(gzip -c "$SIZE_DIR/build/lite.min.js" | wc -c | tr -d ' ')
LITE_GZ_KB=$(echo "scale=1; $LITE_GZ/1024" | bc)

FULL_RAW=$(wc -c < "$SIZE_DIR/build/full.js" | tr -d ' ')
FULL_MIN=$(wc -c < "$SIZE_DIR/build/full.min.js" | tr -d ' ')
FULL_GZ=$(gzip -c "$SIZE_DIR/build/full.min.js" | wc -c | tr -d ' ')
FULL_GZ_KB=$(echo "scale=1; $FULL_GZ/1024" | bc)

SAVINGS=$((FULL_GZ - LITE_GZ))
SAVINGS_PCT=$((SAVINGS * 100 / FULL_GZ))

echo ""
printf "  %-10s %10s %10s %10s\n" "" "Raw" "Minified" "Gzipped"
printf "  %-10s %10s %10s %10s\n" "---" "---" "---" "---"
printf "  %-10s %9sB %9sB %7s KB\n" "Full" "$FULL_RAW" "$FULL_MIN" "$FULL_GZ_KB"
printf "  %-10s %9sB %9sB %7s KB\n" "Lite" "$LITE_RAW" "$LITE_MIN" "$LITE_GZ_KB"
echo ""
echo "  Savings: ${SAVINGS}B gzipped (${SAVINGS_PCT}%)"

if [ "$(echo "$LITE_GZ_KB < 8.0" | bc)" -eq 1 ]; then
  record "Size: lite < 8 KB gzipped" "PASS" "${LITE_GZ_KB} KB"
else
  record "Size: lite < 8 KB gzipped" "FAIL" "${LITE_GZ_KB} KB"
fi

# Check no dev code in lite bundle (check for actual function/class names, not substrings)
if grep -qE "AvoEncryption|encryptValue|EventSpecCache|AvoEventSpecFetcher|EventValidator|validateEvent" "$SIZE_DIR/build/lite.min.js"; then
  record "Size: no dev code in minified lite bundle" "FAIL"
else
  record "Size: no dev code in minified lite bundle" "PASS"
fi

cd "$(dirname "$0")/.."
rm -rf "$SIZE_DIR"
fi
echo ""

# ─── Step 5: Bundler compatibility ───────────────────────────────
echo "▸ Step 5: Bundler compatibility (esbuild, webpack)"

COMPAT_DIR=$(mktemp -d)
cat > "$COMPAT_DIR/package.json" <<PKGJSON
{ "private": true }
PKGJSON
cd "$COMPAT_DIR"
npm install "$TARBALL_PATH" esbuild webpack webpack-cli --save > /dev/null 2>&1

cat > "$COMPAT_DIR/entry.js" <<'ENTRYJS'
const { AvoInspector, AvoInspectorEnv } = require("avo-inspector/lite");
module.exports = { AvoInspector, AvoInspectorEnv };
ENTRYJS

mkdir -p "$COMPAT_DIR/build"

# esbuild
if npx esbuild "$COMPAT_DIR/entry.js" --bundle --platform=node --outfile="$COMPAT_DIR/build/esbuild.js" 2>/dev/null; then
  record "Compat: esbuild bundles avo-inspector/lite" "PASS"
else
  record "Compat: esbuild bundles avo-inspector/lite" "FAIL"
fi

# webpack
cat > "$COMPAT_DIR/webpack.config.js" <<WPCFG
const path = require("path");
module.exports = {
  mode: "production",
  entry: "./entry.js",
  output: { filename: "webpack.js", path: path.resolve("$COMPAT_DIR", "build"), libraryTarget: "umd" },
};
WPCFG
if npx webpack --config "$COMPAT_DIR/webpack.config.js" 2>/dev/null; then
  record "Compat: webpack bundles avo-inspector/lite" "PASS"
else
  record "Compat: webpack bundles avo-inspector/lite" "FAIL"
fi

# Node.js: exports map blocks deep imports (expected — consumers use avo-inspector/lite)
if node -e "require('avo-inspector/dist/lite/index.js')" 2>/dev/null; then
  record "Compat: exports map blocks deep imports" "FAIL" "deep import should be blocked"
else
  record "Compat: exports map blocks deep imports (expected)" "PASS"
fi

cd "$(dirname "$0")/.."
rm -rf "$COMPAT_DIR"
echo ""

# ─── Step 6: npm pack contents ───────────────────────────────────
echo "▸ Step 6: Package contents"

LITE_FILE_COUNT=$(npm pack --dry-run 2>&1 | grep "dist/lite" | wc -l | tr -d ' ')
if [ "$LITE_FILE_COUNT" -ge 10 ]; then
  record "Pack: dist/lite/ files included" "PASS" "$LITE_FILE_COUNT files"
else
  record "Pack: dist/lite/ files included" "FAIL" "only $LITE_FILE_COUNT files"
fi

TARBALL_SIZE=$(wc -c < "$TARBALL" | tr -d ' ')
TARBALL_KB=$(echo "scale=1; $TARBALL_SIZE/1024" | bc)
record "Pack: tarball size" "PASS" "${TARBALL_KB} KB"

# ─── Results ──────────────────────────────────────────────────────
echo ""
echo "============================================="
echo "  QA Results: $PASS passed, $FAIL failed"
echo "============================================="
echo -e "$RESULTS"
echo ""

rm -f "$TARBALL"

if [ "$FAIL" -gt 0 ]; then
  echo "❌ QA FAILED"
  exit 1
else
  echo "✅ QA PASSED"
fi
