import { readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { MENU_CODE_VALUES, PERMISSION_CODES } from "../src/app/permissions";
import { fallbackNavigation } from "../src/app/navigation/menu";
import { adminRoutes } from "../src/app/router/routeConfig";

type OpenApiOperation = {
  operationId?: string;
  "x-permission"?: string;
  "x-permissions"?: string[];
};

type OpenApiDocument = {
  paths?: Record<string, Record<string, OpenApiOperation>>;
};

type NavItem = {
  children?: NavItem[];
  code?: string;
  path?: string;
  permissionCode?: string;
};

const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const contract = JSON.parse(readFileSync(path.join(rootDir, "contracts/admin-openapi.json"), "utf8")) as OpenApiDocument;
const errors: string[] = [];
const knownPermissions = new Set<string>(PERMISSION_CODES);
const knownMenuCodes = new Set<string>(MENU_CODE_VALUES);
const routesByPath = new Map<string, (typeof adminRoutes)[number]>();
const routesByMenuCode = new Map<string, (typeof adminRoutes)[number]>();
const routePermissions = new Set<string>();

for (const route of adminRoutes) {
  if (!route.path) {
    errors.push(`Route ${route.pageKey} is missing path`);
  }
  if (!route.menuCode) {
    errors.push(`Route ${route.path} is missing menuCode`);
  }
  if (!route.permission) {
    errors.push(`Route ${route.path} is missing permission`);
  }
  if (route.menuCode && !knownMenuCodes.has(route.menuCode)) {
    errors.push(`Route ${route.path} uses unknown menuCode: ${route.menuCode}`);
  }
  if (route.permission && !knownPermissions.has(route.permission)) {
    errors.push(`Route ${route.path} uses unknown permission: ${route.permission}`);
  }
  if (routesByPath.has(route.path)) {
    errors.push(`Duplicate route path: ${route.path}`);
  }
  if (routesByMenuCode.has(route.menuCode)) {
    errors.push(`Duplicate route menuCode: ${route.menuCode}`);
  }

  routesByPath.set(route.path, route);
  routesByMenuCode.set(route.menuCode, route);
  routePermissions.add(route.permission);
}

for (const item of flattenNavItems(fallbackNavigation as NavItem[])) {
  if (item.code && !knownMenuCodes.has(item.code)) {
    errors.push(`Fallback menu uses unknown menu code: ${item.code}`);
  }

  if (!item.path) {
    continue;
  }

  const route = item.code ? routesByMenuCode.get(item.code) : undefined;
  if (!route) {
    errors.push(`Fallback menu ${item.code || item.path} has no matching route`);
    continue;
  }
  if (route.path !== item.path) {
    errors.push(`Fallback menu ${item.code} path mismatch: ${item.path} !== ${route.path}`);
  }
  if (item.permissionCode !== route.permission) {
    errors.push(`Fallback menu ${item.code} permission mismatch: ${item.permissionCode} !== ${route.permission}`);
  }
}

const operationIds = new Set<string>();
const apiPermissions = new Set<string>();
const httpMethods = new Set(["delete", "get", "patch", "post", "put"]);

for (const [endpointPath, pathItem] of Object.entries(contract.paths || {})) {
  for (const [method, operation] of Object.entries(pathItem)) {
    if (!httpMethods.has(method)) {
      continue;
    }
    if (!operation.operationId) {
      errors.push(`${method.toUpperCase()} ${endpointPath} is missing operationId`);
    } else if (operationIds.has(operation.operationId)) {
      errors.push(`Duplicate OpenAPI operationId: ${operation.operationId}`);
    } else {
      operationIds.add(operation.operationId);
    }

    const permissions = operation["x-permissions"]?.length
      ? operation["x-permissions"]
      : operation["x-permission"]
        ? [operation["x-permission"]]
        : [];
    for (const permission of permissions) {
      apiPermissions.add(permission);
      if (!knownPermissions.has(permission)) {
        errors.push(`${method.toUpperCase()} ${endpointPath} uses unknown x-permission: ${permission}`);
      }
    }
  }
}

if (errors.length) {
  console.error("Admin architecture validation failed:");
  for (const error of errors) {
    console.error(`- ${error}`);
  }
  process.exit(1);
}

console.log(
  `Admin architecture validation passed: ${adminRoutes.length} routes, ${operationIds.size} operations, ${apiPermissions.size} protected permissions.`
);

function flattenNavItems(items: NavItem[]): NavItem[] {
  return items.flatMap((item) => [item, ...flattenNavItems(item.children || [])]);
}
