/**
 * Zenith Silver E2E Test Runner & Assertion Library
 * High-performance, zero-dependency async test framework for Receipt Scanner to Excel
 */

// ANSI Color Codes
const RESET = "\x1b[0m";
const BOLD = "\x1b[1m";
const DIM = "\x1b[2m";
const RED = "\x1b[31m";
const GREEN = "\x1b[32m";
const YELLOW = "\x1b[33m";
const BLUE = "\x1b[34m";
const MAGENTA = "\x1b[35m";
const CYAN = "\x1b[36m";
const WHITE = "\x1b[37m";
const BG_RED = "\x1b[41m";
const BG_GREEN = "\x1b[42m";

export type TestFn = () => void | Promise<void>;
export type HookFn = () => void | Promise<void>;

export interface TestCase {
  name: string;
  fn: TestFn;
  durationMs?: number;
  error?: Error;
  passed?: boolean;
}

export interface TestSuite {
  name: string;
  tests: TestCase[];
  beforeAllHooks: HookFn[];
  afterAllHooks: HookFn[];
  beforeEachHooks: HookFn[];
  afterEachHooks: HookFn[];
  parent?: TestSuite;
  children: TestSuite[];
}

class TestRegistry {
  suites: TestSuite[] = [];
  currentSuite: TestSuite | null = null;

  createSuite(name: string, parent?: TestSuite): TestSuite {
    return {
      name,
      tests: [],
      beforeAllHooks: [],
      afterAllHooks: [],
      beforeEachHooks: [],
      afterEachHooks: [],
      parent,
      children: [],
    };
  }
}

const registry = new TestRegistry();

export function describe(name: string, fn: () => void) {
  const suite = registry.createSuite(name, registry.currentSuite || undefined);
  if (registry.currentSuite) {
    registry.currentSuite.children.push(suite);
  } else {
    registry.suites.push(suite);
  }

  const previousSuite = registry.currentSuite;
  registry.currentSuite = suite;
  try {
    fn();
  } finally {
    registry.currentSuite = previousSuite;
  }
}

export function test(name: string, fn: TestFn) {
  if (!registry.currentSuite) {
    const rootSuite = registry.createSuite("Root Suite");
    registry.suites.push(rootSuite);
    registry.currentSuite = rootSuite;
  }
  registry.currentSuite.tests.push({ name, fn });
}

export const it = test;

export function beforeAll(fn: HookFn) {
  if (registry.currentSuite) {
    registry.currentSuite.beforeAllHooks.push(fn);
  }
}

export function afterAll(fn: HookFn) {
  if (registry.currentSuite) {
    registry.currentSuite.afterAllHooks.push(fn);
  }
}

export function beforeEach(fn: HookFn) {
  if (registry.currentSuite) {
    registry.currentSuite.beforeEachHooks.push(fn);
  }
}

export function afterEach(fn: HookFn) {
  if (registry.currentSuite) {
    registry.currentSuite.afterEachHooks.push(fn);
  }
}

// ============================================================================
// ASSERTION LIBRARY (expect)
// ============================================================================

export class AssertionError extends Error {
  constructor(message: string, public actual?: any, public expected?: any) {
    super(message);
    this.name = "AssertionError";
  }
}

function deepEqual(a: any, b: any): boolean {
  if (a === b) return true;
  if (a == null || b == null) return false;
  if (typeof a !== "object" || typeof b !== "object") return false;

  if (Array.isArray(a) !== Array.isArray(b)) return false;
  if (Array.isArray(a)) {
    if (a.length !== b.length) return false;
    for (let i = 0; i < a.length; i++) {
      if (!deepEqual(a[i], b[i])) return false;
    }
    return true;
  }

  if (a instanceof Date && b instanceof Date) {
    return a.getTime() === b.getTime();
  }

  if (a instanceof RegExp && b instanceof RegExp) {
    return a.toString() === b.toString();
  }

  const keysA = Object.keys(a);
  const keysB = Object.keys(b);
  if (keysA.length !== keysB.length) return false;

  for (const key of keysA) {
    if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
    if (!deepEqual(a[key], b[key])) return false;
  }

  return true;
}

export interface Matchers<T> {
  toBe(expected: any): void;
  toEqual(expected: any): void;
  toStrictEqual(expected: any): void;
  toBeCloseTo(expected: number, deltaOrDigits?: number): void;
  toBeGreaterThan(expected: number): void;
  toBeGreaterThanOrEqual(expected: number): void;
  toBeLessThan(expected: number): void;
  toBeLessThanOrEqual(expected: number): void;
  toBeTruthy(): void;
  toBeFalsy(): void;
  toBeNull(): void;
  toBeUndefined(): void;
  toBeDefined(): void;
  toContain(expected: any): void;
  toHaveLength(expected: number): void;
  toMatch(regex: RegExp | string): void;
  toBeInstanceOf(expected: any): void;
  toThrow(expectedError?: string | RegExp | Function): void;
  not: Matchers<T>;
}

export function expect<T = any>(actual: T): Matchers<T> {
  const createMatcher = (isNot: boolean): Matchers<T> => {
    return {
      toBe(expected: any) {
        const pass = Object.is(actual, expected);
        if (isNot ? pass : !pass) {
          throw new AssertionError(
            `Expected ${JSON.stringify(actual)} ${isNot ? "NOT to be" : "to be"} ${JSON.stringify(expected)}`,
            actual,
            expected
          );
        }
      },

      toEqual(expected: any) {
        const pass = deepEqual(actual, expected);
        if (isNot ? pass : !pass) {
          throw new AssertionError(
            `Expected ${JSON.stringify(actual)} ${isNot ? "NOT to equal" : "to equal"} ${JSON.stringify(expected)}`,
            actual,
            expected
          );
        }
      },

      toStrictEqual(expected: any) {
        const pass = deepEqual(actual, expected);
        if (isNot ? pass : !pass) {
          throw new AssertionError(
            `Expected ${JSON.stringify(actual)} ${isNot ? "NOT to strictly equal" : "to strictly equal"} ${JSON.stringify(expected)}`,
            actual,
            expected
          );
        }
      },

      toBeCloseTo(expected: number, deltaOrDigits: number = 2) {
        if (typeof actual !== "number") {
          throw new AssertionError(`Actual value ${actual} is not a number`);
        }
        // If deltaOrDigits <= 0.1, treat as delta, otherwise decimal digits
        const delta = deltaOrDigits < 1 ? deltaOrDigits : Math.pow(10, -deltaOrDigits) / 2;
        const diff = Math.abs(actual - expected);
        const pass = diff <= delta;
        if (isNot ? pass : !pass) {
          throw new AssertionError(
            `Expected ${actual} ${isNot ? "NOT to be close to" : "to be close to"} ${expected} (diff: ${diff.toFixed(4)}, max allowed: ${delta})`,
            actual,
            expected
          );
        }
      },

      toBeGreaterThan(expected: number) {
        const pass = (actual as any) > expected;
        if (isNot ? pass : !pass) {
          throw new AssertionError(
            `Expected ${actual} ${isNot ? "NOT to be >" : "to be >"} ${expected}`,
            actual,
            expected
          );
        }
      },

      toBeGreaterThanOrEqual(expected: number) {
        const pass = (actual as any) >= expected;
        if (isNot ? pass : !pass) {
          throw new AssertionError(
            `Expected ${actual} ${isNot ? "NOT to be >=" : "to be >="} ${expected}`,
            actual,
            expected
          );
        }
      },

      toBeLessThan(expected: number) {
        const pass = (actual as any) < expected;
        if (isNot ? pass : !pass) {
          throw new AssertionError(
            `Expected ${actual} ${isNot ? "NOT to be <" : "to be <"} ${expected}`,
            actual,
            expected
          );
        }
      },

      toBeLessThanOrEqual(expected: number) {
        const pass = (actual as any) <= expected;
        if (isNot ? pass : !pass) {
          throw new AssertionError(
            `Expected ${actual} ${isNot ? "NOT to be <=" : "to be <="} ${expected}`,
            actual,
            expected
          );
        }
      },

      toBeTruthy() {
        const pass = Boolean(actual);
        if (isNot ? pass : !pass) {
          throw new AssertionError(
            `Expected ${JSON.stringify(actual)} ${isNot ? "to be falsy" : "to be truthy"}`,
            actual,
            !isNot
          );
        }
      },

      toBeFalsy() {
        const pass = !Boolean(actual);
        if (isNot ? pass : !pass) {
          throw new AssertionError(
            `Expected ${JSON.stringify(actual)} ${isNot ? "to be truthy" : "to be falsy"}`,
            actual,
            isNot
          );
        }
      },

      toBeNull() {
        const pass = actual === null;
        if (isNot ? pass : !pass) {
          throw new AssertionError(
            `Expected ${JSON.stringify(actual)} ${isNot ? "NOT to be null" : "to be null"}`,
            actual,
            null
          );
        }
      },

      toBeUndefined() {
        const pass = actual === undefined;
        if (isNot ? pass : !pass) {
          throw new AssertionError(
            `Expected ${actual} ${isNot ? "NOT to be undefined" : "to be undefined"}`,
            actual,
            undefined
          );
        }
      },

      toBeDefined() {
        const pass = actual !== undefined;
        if (isNot ? pass : !pass) {
          throw new AssertionError(
            `Expected value ${isNot ? "to be undefined" : "to be defined"}`,
            actual,
            "defined"
          );
        }
      },

      toContain(expected: any) {
        let pass = false;
        if (typeof actual === "string") {
          pass = actual.includes(String(expected));
        } else if (Array.isArray(actual)) {
          pass = actual.some((item) => deepEqual(item, expected));
        } else if (actual instanceof Set || actual instanceof Map) {
          pass = (actual as any).has(expected);
        }
        if (isNot ? pass : !pass) {
          throw new AssertionError(
            `Expected container ${isNot ? "NOT to contain" : "to contain"} ${JSON.stringify(expected)}`,
            actual,
            expected
          );
        }
      },

      toHaveLength(expected: number) {
        const len = (actual as any)?.length ?? (actual as any)?.size;
        const pass = len === expected;
        if (isNot ? pass : !pass) {
          throw new AssertionError(
            `Expected length ${isNot ? "NOT to be" : "to be"} ${expected}, but received ${len}`,
            len,
            expected
          );
        }
      },

      toMatch(regex: RegExp | string) {
        const str = String(actual);
        const re = typeof regex === "string" ? new RegExp(regex) : regex;
        const pass = re.test(str);
        if (isNot ? pass : !pass) {
          throw new AssertionError(
            `Expected string "${str}" ${isNot ? "NOT to match" : "to match"} pattern ${re}`,
            actual,
            regex
          );
        }
      },

      toBeInstanceOf(expected: any) {
        const pass = actual instanceof expected;
        if (isNot ? pass : !pass) {
          throw new AssertionError(
            `Expected object ${isNot ? "NOT to be instance of" : "to be instance of"} ${expected?.name || expected}`,
            actual,
            expected
          );
        }
      },

      toThrow(expectedError?: string | RegExp | Function) {
        if (typeof actual !== "function") {
          throw new AssertionError(`Actual target is not a function: ${typeof actual}`);
        }
        let threw = false;
        let caughtError: any = null;
        try {
          (actual as any)();
        } catch (err) {
          threw = true;
          caughtError = err;
        }

        if (isNot) {
          if (threw) {
            throw new AssertionError(
              `Expected function NOT to throw, but it threw: ${caughtError?.message || caughtError}`
            );
          }
          return;
        }

        if (!threw) {
          throw new AssertionError(`Expected function to throw an error, but it returned cleanly`);
        }

        if (expectedError) {
          const msg = caughtError?.message || String(caughtError);
          if (typeof expectedError === "string" && !msg.includes(expectedError)) {
            throw new AssertionError(
              `Expected thrown error message to contain "${expectedError}", received: "${msg}"`
            );
          } else if (expectedError instanceof RegExp && !expectedError.test(msg)) {
            throw new AssertionError(
              `Expected thrown error message to match ${expectedError}, received: "${msg}"`
            );
          } else if (typeof expectedError === "function" && !(caughtError instanceof expectedError)) {
            throw new AssertionError(
              `Expected thrown error to be instance of ${expectedError.name}, received: ${caughtError?.name}`
            );
          }
        }
      },

      get not() {
        return createMatcher(!isNot);
      },
    };
  };

  return createMatcher(false);
}

// ============================================================================
// SUITE EXECUTION ENGINE
// ============================================================================

export interface RunResults {
  totalSuites: number;
  totalTests: number;
  passedCount: number;
  failedCount: number;
  durationMs: number;
  failures: { suiteName: string; testName: string; error: Error }[];
}

async function runSuite(
  suite: TestSuite,
  results: RunResults,
  indent = ""
): Promise<void> {
  results.totalSuites++;
  console.log(`\n${indent}${BOLD}${CYAN}▸ ${suite.name}${RESET}`);

  // Run beforeAll hooks
  for (const hook of suite.beforeAllHooks) {
    try {
      await hook();
    } catch (err: any) {
      console.error(`${indent}  ${RED}✖ [beforeAll Hook Failed]${RESET}`, err.message);
      throw err;
    }
  }

  // Run suite tests
  for (const testCase of suite.tests) {
    results.totalTests++;

    // Run beforeEach hooks
    for (const hook of suite.beforeEachHooks) {
      await hook();
    }

    const startTime = performance.now();
    try {
      await testCase.fn();
      testCase.durationMs = performance.now() - startTime;
      testCase.passed = true;
      results.passedCount++;

      const timeStr = testCase.durationMs > 100
        ? `${YELLOW}(${testCase.durationMs.toFixed(1)}ms)${RESET}`
        : `${DIM}(${testCase.durationMs.toFixed(1)}ms)${RESET}`;

      console.log(`${indent}  ${GREEN}✓${RESET} ${testCase.name} ${timeStr}`);
    } catch (err: any) {
      testCase.durationMs = performance.now() - startTime;
      testCase.passed = false;
      testCase.error = err;
      results.failedCount++;
      results.failures.push({
        suiteName: suite.name,
        testName: testCase.name,
        error: err,
      });

      console.log(`${indent}  ${RED}✖ ${testCase.name}${RESET} ${RED}(${testCase.durationMs.toFixed(1)}ms)${RESET}`);
      console.log(`${indent}    ${RED}${err.name}: ${err.message}${RESET}`);
      if (err.stack) {
        const stackLines = err.stack.split("\n").slice(1, 4).map((l: string) => `${indent}      ${DIM}${l.trim()}${RESET}`);
        console.log(stackLines.join("\n"));
      }
    }

    // Run afterEach hooks
    for (const hook of suite.afterEachHooks) {
      await hook();
    }
  }

  // Run nested suites
  for (const childSuite of suite.children) {
    await runSuite(childSuite, results, indent + "  ");
  }

  // Run afterAll hooks
  for (const hook of suite.afterAllHooks) {
    try {
      await hook();
    } catch (err: any) {
      console.error(`${indent}  ${RED}✖ [afterAll Hook Failed]${RESET}`, err.message);
    }
  }
}

export async function runAllTests(filterPattern?: string): Promise<boolean> {
  const globalStartTime = performance.now();

  console.log(`\n${BOLD}${WHITE}================================================================================${RESET}`);
  console.log(`${BOLD}${CYAN}   ZENITH SILVER RECEIPT SCANNER — END-TO-END VERIFICATION SUITE   ${RESET}`);
  console.log(`${BOLD}${WHITE}================================================================================${RESET}`);
  console.log(`${DIM}Runner: Native Async TypeScript • Date: ${new Date().toISOString()}${RESET}\n`);

  const results: RunResults = {
    totalSuites: 0,
    totalTests: 0,
    passedCount: 0,
    failedCount: 0,
    durationMs: 0,
    failures: [],
  };

  const suitesToRun = filterPattern
    ? registry.suites.filter((s) => s.name.toLowerCase().includes(filterPattern.toLowerCase()))
    : registry.suites;

  for (const suite of suitesToRun) {
    await runSuite(suite, results);
  }

  results.durationMs = performance.now() - globalStartTime;

  console.log(`\n${BOLD}${WHITE}================================================================================${RESET}`);
  console.log(`${BOLD}TEST RUN SUMMARY${RESET}`);
  console.log(`${BOLD}${WHITE}================================================================================${RESET}`);
  console.log(`Total Suites : ${results.totalSuites}`);
  console.log(`Total Tests  : ${results.totalTests}`);
  console.log(`Passed Tests : ${GREEN}${BOLD}${results.passedCount} ✓${RESET}`);
  console.log(`Failed Tests : ${results.failedCount > 0 ? `${RED}${BOLD}${results.failedCount} ✖${RESET}` : `${GREEN}0${RESET}`}`);
  console.log(`Total Time   : ${(results.durationMs / 1000).toFixed(2)}s (${results.durationMs.toFixed(1)} ms)`);

  if (results.failures.length > 0) {
    console.log(`\n${BOLD}${RED}FAILED TESTS SUMMARY (${results.failures.length}):${RESET}`);
    results.failures.forEach((f, idx) => {
      console.log(`\n  ${RED}${idx + 1}) [${f.suiteName}] ${f.testName}${RESET}`);
      console.log(`     ${RED}${f.error.name}: ${f.error.message}${RESET}`);
    });
  }

  const allPassed = results.failedCount === 0 && results.totalTests > 0;

  if (allPassed) {
    console.log(`\n${BG_GREEN}${BOLD}${WHITE} ALL ${results.totalTests} TESTS PASSED CLEANLY (100% VERIFIED) ${RESET}\n`);
  } else {
    console.log(`\n${BG_RED}${BOLD}${WHITE} TEST SUITE FAILED WITH ${results.failedCount} ERRORS ${RESET}\n`);
  }

  return allPassed;
}

// Automatically execute if run directly
async function main() {
  const args = process.argv.slice(2);
  const filter = args[0];

  // Import test suites dynamically if this is the entry point
  try {
    await import("./tier1_features.test");
    await import("./tier2_boundaries.test");
    await import("./tier3_interactions.test");
    await import("./tier4_workloads.test");
    await import("../../src/components/dashboard/__tests__/batchUpload.test");
    await import("./m1_adversarial.test");
    await import("./challenger2_stress.test");
    await import("../../src/components/dashboard/__tests__/inspectorModal.test");
    await import("./m2_adversarial.test");
    await import("../../src/components/dashboard/__tests__/liveTable.test");
    await import("./m3_adversarial.test");
    await import("./challenger_m3_stress");
    await import("./m3_challenger_deep_stress.test");
    await import("../../src/components/dashboard/__tests__/responsiveShell.test");
    await import("./m4_adversarial.test");
    await import("./challenger_m4_stress");
    await import("./challenger_m4_2_stress");
    await import("./challenger_m4_1_deep_stress");
    await import("./extraction_quality.test");
    await import("./export_localization.test");
    await import("./export_pdf.test");
    await import("./challenger_excel_adversarial.test");
    await import("./auth_security.test");
    await import("./security_headers.test");
    await import("./csrf_tokens.test");
    await import("./subdomain_routing.test");
    await import("./seo_slugs.test");
    await import("./user_enumeration.test");
    await import("./upload_whitelist.test");
    await import("./sprint_a_scanner.test");
    await import("./sprint_b_speed.test");
    await import("./sprint_c_image.test");
    await import("./sprint_e.test");
    await import("./server_pricing.test");
    await import("./webhook_verification.test");
  } catch (err) {
    console.error("Failed to load test suite modules:", err);
    process.exit(1);
  }

  const success = await runAllTests(filter);
  if (!success) {
    process.exit(1);
  }
}

// Check if current file is the main entry point
if (typeof require !== "undefined" && require.main === module) {
  main().catch((err) => {
    console.error("Fatal runner crash:", err);
    process.exit(1);
  });
} else if (typeof process !== "undefined" && process.argv && process.argv[1]?.includes("runner.ts")) {
  main().catch((err) => {
    console.error("Fatal runner crash:", err);
    process.exit(1);
  });
}
