import path from "node:path";
import fs from "node:fs";
import fg from "fast-glob";
import { parseTestCasesFromDoc, parseItDescriptionsFromTest } from "../parse-doc-test-cases";
import { moduleTestCaseCategories, appTestCaseCategories } from "./discovery";
import type { CheckError } from "./sync-check-source";

function toCamelCase(pascalCase: string): string {
  return pascalCase.charAt(0).toLowerCase() + pascalCase.slice(1);
}

export async function runModuleTestCaseSyncCheck(root: string, cwd: string): Promise<CheckError[]> {
  const errors: CheckError[] = [];
  const categories = moduleTestCaseCategories(root);

  for (const category of categories) {
    const docPaths = await fg(category.docPattern, { cwd });

    for (const docPath of docPaths) {
      const docFullPath = path.join(cwd, docPath);
      const docContent = fs.readFileSync(docFullPath, "utf-8");
      const docTestCases = parseTestCasesFromDoc(docContent);

      if (docTestCases.length === 0) continue;

      const docBasename = path.basename(docPath, ".md");
      const docsIndex = docPath.indexOf("/docs/");
      if (docsIndex === -1) continue;
      const modulePath = docPath.substring(0, docsIndex);
      const testFileName = `${toCamelCase(docBasename)}.test.ts`;
      const testPath = path.join(modulePath, category.testDir, testFileName);
      const testFullPath = path.join(cwd, testPath);

      if (!fs.existsSync(testFullPath)) {
        errors.push({
          type: "missing-test-file",
          category: category.name,
          docPath,
          sourcePath: testPath,
          expectedBasename: testFileName,
        });
        continue;
      }

      const testContent = fs.readFileSync(testFullPath, "utf-8");
      const itDescriptions = parseItDescriptionsFromTest(testContent);

      const docSet = new Set(docTestCases);
      const testSet = new Set(itDescriptions);

      for (const docCase of docSet) {
        if (!testSet.has(docCase)) {
          errors.push({
            type: "missing-test-case",
            category: category.name,
            docPath,
            sourcePath: testPath,
            expectedBasename: docCase,
          });
        }
      }

      for (const testCase of testSet) {
        if (!docSet.has(testCase)) {
          errors.push({
            type: "extra-test-case",
            category: category.name,
            docPath,
            sourcePath: testPath,
            expectedBasename: testCase,
          });
        }
      }
    }
  }

  return errors;
}

function resolveStoryTestPath(docPath: string, testDir: string): string | null {
  const regex = /^(.+)\/docs\/business-flow\/([^/]+)\/story\/([^/]+)\.md$/;
  const match = regex.exec(docPath);
  if (!match) return null;
  const [, appPath, flow, name] = match;
  return `${appPath}/${testDir}/${flow}/${name}.test.ts`;
}

export async function runAppTestCaseSyncCheck(appRoot: string, cwd: string): Promise<CheckError[]> {
  const errors: CheckError[] = [];
  const config = appTestCaseCategories(appRoot);
  const docPaths = await fg(config.docPattern, { cwd });

  for (const docPath of docPaths) {
    const docFullPath = path.join(cwd, docPath);
    const docContent = fs.readFileSync(docFullPath, "utf-8");
    const docTestCases = parseTestCasesFromDoc(docContent);

    if (docTestCases.length === 0) continue;

    const testPath = resolveStoryTestPath(docPath, config.testDir);
    if (!testPath) continue;

    const testFullPath = path.join(cwd, testPath);
    if (!fs.existsSync(testFullPath)) {
      errors.push({
        type: "missing-test-file",
        category: config.name,
        docPath,
        sourcePath: testPath,
        expectedBasename: path.basename(testPath),
      });
      continue;
    }

    const testContent = fs.readFileSync(testFullPath, "utf-8");
    const itDescriptions = parseItDescriptionsFromTest(testContent);

    const docSet = new Set(docTestCases);
    const testSet = new Set(itDescriptions);

    for (const docCase of docSet) {
      if (!testSet.has(docCase)) {
        errors.push({
          type: "missing-test-case",
          category: config.name,
          docPath,
          sourcePath: testPath,
          expectedBasename: docCase,
        });
      }
    }

    for (const testCase of testSet) {
      if (!docSet.has(testCase)) {
        errors.push({
          type: "extra-test-case",
          category: config.name,
          docPath,
          sourcePath: testPath,
          expectedBasename: testCase,
        });
      }
    }
  }

  return errors;
}
