import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { definePlugin, type PluginContext } from "@wrnexus/plugin";
import type { AuthEngine } from "./engine.ts";
import type { AuthPasskeyHttpOptions } from "./http/index.ts";
import type {
  AuthSessionVerificationHandler,
  AuthSignedInHandler,
  AuthSignedOutHandler,
} from "./types.ts";
import { AUTH_ROUTE_DEFINITIONS, type AuthRouteGroup } from "./routes/definitions.ts";
import {
  clearDefaultAuthEngine,
  setDefaultAuthEngine,
  setDefaultAuthRouteOptions,
  setDefaultAuthSchemas,
} from "./runtime.ts";
import {
  authBrowserSchemaDescriptors,
  resolveAuthSchemas,
  type AuthSchemaOverrides,
  type AuthSchemaSet,
} from "./validation.ts";

export interface AuthRoutesConfig {
  enabled?: boolean;
  registration?: boolean;
  login?: boolean;
  verification?: boolean;
  password?: boolean;
  invitations?: boolean;
  magicLink?: boolean;
  otp?: boolean;
  mfa?: boolean;
  sessions?: boolean;
  impersonation?: boolean;
  passkeys?: boolean;
}

export interface AuthConfig {
  enabled?: boolean;
  engine?: AuthEngine;
  routes?: boolean | AuthRoutesConfig;
  migrations?: boolean;
  middleware?: boolean;
  components?: boolean;
  client?: boolean;
  devToolbar?: boolean;
  componentDir?: string;
  schemas?: AuthSchemaOverrides;
  baseUrl?: string;
  csrf?: boolean;
  passkey?: AuthPasskeyHttpOptions;
  /** @deprecated Prefer createAuthEngine({ onSignedIn }). */
  onSignedIn?: AuthSignedInHandler;
  /** @deprecated Prefer createAuthEngine({ onSignedOut }). */
  onSignedOut?: AuthSignedOutHandler;
  /** Shared SSO cookie whose value is an AuthEngine session id. */
  sessionCookieName?: string;
  /** Customize the package forward-auth verification response. */
  onSessionVerification?: AuthSessionVerificationHandler;
}

/** Explicit plugin options remain supported for compatibility. Prefer config.auth. */
export interface AuthPluginOptions {
  componentDir?: string;
  exposeComponentDirectory?: boolean;
  enableDevToolbar?: boolean;
  includeMigrations?: boolean;
  includeRoutes?: boolean;
  includeMiddleware?: boolean;
}

export interface AuthAuditIssue {
  id: string;
  severity: "error" | "warning" | "suggestion";
  title: string;
  message: string;
  file: string;
}

interface ResolvedAuthConfig {
  enabled: boolean;
  engine?: AuthEngine;
  routes: boolean | AuthRoutesConfig;
  migrations: boolean;
  middleware: boolean;
  components: boolean;
  client: boolean;
  devToolbar: boolean;
  componentDir: string;
  schemas: AuthSchemaSet;
  baseUrl?: string;
  csrf: boolean;
  passkey?: AuthPasskeyHttpOptions;
  onSignedIn?: AuthSignedInHandler;
  onSignedOut?: AuthSignedOutHandler;
  sessionCookieName?: string;
  onSessionVerification?: AuthSessionVerificationHandler;
}

const moduleRoot = dirname(fileURLToPath(import.meta.url));
const packageRoot = dirname(moduleRoot);
const compiledPackage = moduleRoot === join(packageRoot, "dist");
const clientRuntime = join(packageRoot, "assets", "client", "auth.js");
const migrationsFile = join(packageRoot, "migrations", "001_auth.sql");
const otpPurposeMigrationFile = join(packageRoot, "migrations", "002_auth_otp_purpose.sql");
const apiRoutesDir = compiledPackage
  ? join(moduleRoot, "routes", "api")
  : join(packageRoot, "src", "routes", "api");
const middlewareFile = compiledPackage
  ? join(moduleRoot, "routes", "middleware.js")
  : join(packageRoot, "src", "routes", "middleware.ts");
const resolvedConfigKey = "@wrnexus/auth:resolved-config";

export function authComponentsDir(): string {
  return join(packageRoot, "components");
}

function authApiRouteEntry(path: string): string {
  const routeName = path.replace(/^\/api\/auth\/?/, "").replace(/\//g, "-") || "index";
  return join(apiRoutesDir, routeName + (compiledPackage ? ".js" : ".ts"));
}

function resolveConfig(
  config: Record<string, unknown>,
  options: AuthPluginOptions,
): ResolvedAuthConfig {
  const raw = (config.auth ?? {}) as AuthConfig;
  const enabled = raw.enabled !== false;
  const hasEngine = Boolean(raw.engine);
  const hasDefaultDb = Boolean(config.db);

  return {
    enabled,
    engine: raw.engine,
    routes: options.includeRoutes !== undefined ? options.includeRoutes : (raw.routes ?? hasEngine),
    migrations:
      options.includeMigrations !== undefined
        ? options.includeMigrations
        : (raw.migrations ?? (hasEngine && hasDefaultDb)),
    middleware:
      options.includeMiddleware !== undefined
        ? options.includeMiddleware
        : (raw.middleware ?? hasEngine),
    components:
      options.exposeComponentDirectory !== undefined
        ? options.exposeComponentDirectory
        : (raw.components ?? true),
    client: raw.client ?? true,
    devToolbar:
      options.enableDevToolbar !== undefined ? options.enableDevToolbar : (raw.devToolbar ?? true),
    componentDir: options.componentDir ?? raw.componentDir ?? authComponentsDir(),
    schemas: resolveAuthSchemas(raw.schemas),
    baseUrl: raw.baseUrl,
    csrf: raw.csrf ?? true,
    passkey: raw.passkey,
    onSignedIn: raw.onSignedIn ?? raw.engine?.onSignedIn,
    onSignedOut: raw.onSignedOut ?? raw.engine?.onSignedOut,
    sessionCookieName: raw.sessionCookieName,
    onSessionVerification: raw.onSessionVerification,
  };
}

function fallbackConfig(options: AuthPluginOptions): ResolvedAuthConfig {
  return {
    enabled: true,

    // Automatic package discovery must never expose authentication
    // endpoints unless the application configures auth or the developer
    // explicitly enables the routes through plugin options.
    routes: options.includeRoutes ?? false,

    migrations: options.includeMigrations ?? false,

    middleware: options.includeMiddleware ?? false,

    // Components and browser assets are safe to expose automatically.
    components: options.exposeComponentDirectory ?? true,

    client: true,

    devToolbar: options.enableDevToolbar ?? true,

    componentDir: options.componentDir ?? authComponentsDir(),

    schemas: resolveAuthSchemas(),

    csrf: true,
  };
}

function resolved(context: PluginContext, options: AuthPluginOptions): ResolvedAuthConfig {
  return (
    (context.metadata.get(resolvedConfigKey) as ResolvedAuthConfig | undefined) ??
    fallbackConfig(options)
  );
}

function routeEnabled(routes: boolean | AuthRoutesConfig, group: AuthRouteGroup): boolean {
  if (typeof routes === "boolean") return routes;
  if (routes.enabled === false) return false;
  return routes[group] !== false;
}

function authClientSource(schemas: AuthSchemaSet): string {
  const descriptors = JSON.stringify(authBrowserSchemaDescriptors(schemas));

  const runtime = readFileSync(clientRuntime, "utf8");

  return `
(function () {
  var defaults = ${descriptors};

  window.__wireSchemas =
    window.__wireSchemas || {};

  Object.keys(defaults).forEach(
    function (name) {
      if (!(name in window.__wireSchemas)) {
        window.__wireSchemas[name] =
          defaults[name];
      }
    }
  );

  if (
    window.__wireValidate &&
    typeof window.__wireValidate
      .registerSchemas === "function"
  ) {
    window.__wireValidate.registerSchemas(
      defaults,
      document
    );
  } else if (
    window.__wireValidate &&
    typeof window.__wireValidate.init ===
      "function"
  ) {
    window.__wireValidate.init(document);
  }
})();

${runtime}
`;
}

function audit(code: string, file: string): AuthAuditIssue[] {
  const issues: AuthAuditIssue[] = [];
  const push = (id: string, severity: AuthAuditIssue["severity"], title: string, message: string) =>
    issues.push({ id: `${id}:${file}`, severity, title, message, file });

  if (/<SignIn|<SignUp|data-component=["']SignIn|data-component=["']SignUp/.test(code)) {
    if (!/<Captcha|captchaGuard|captchaPageGate/.test(code)) {
      push(
        "captcha-escalation",
        "suggestion",
        "Add adaptive CAPTCHA",
        "Authentication forms should connect suspicious attempts to @wrnexus/captcha.",
      );
    }
    if (!/autocomplete=/.test(code)) {
      push(
        "autocomplete",
        "warning",
        "Credential autocomplete is missing",
        "Use username, current-password, and new-password autocomplete values.",
      );
    }
  }
  if (/secret\s*=|clientSecret\s*=|privateKey\s*=/.test(code) && /\.wrn$/.test(file)) {
    push(
      "client-secret",
      "error",
      "Authentication secret exposed",
      "Never pass server secrets to a .wrn component.",
    );
  }
  if (
    /returnTo|redirect/.test(code) &&
    !/validateOAuthReturnTo|safeReturnTo|startsWith\(["']\//.test(code)
  ) {
    push(
      "open-redirect",
      "suggestion",
      "Confirm redirects are same-origin",
      "Validate returnTo values before redirecting after sign-in.",
    );
  }
  return issues;
}

export function authPlugin(options: AuthPluginOptions = {}) {
  const metadataKey = "@wrnexus/auth:audit";

  return definePlugin({
    name: "@wrnexus/auth",
    version: "0.8.0",
    enforce: "post",

    componentDirs(context) {
      const value = resolved(context, options);
      return value.enabled && value.components ? [value.componentDir] : [];
    },

    clientRuntimes(context) {
      const value = resolved(context, options);
      if (!value.enabled || !value.client) return [];
      return [
        {
          id: "auth",
          source: authClientSource(value.schemas),
          type: "script" as const,
          load: "defer" as const,
          singleton: true,
          bundle: false,
        },
      ];
    },

    styleSources(context) {
      const value = resolved(context, options);
      return value.enabled && value.components
        ? [{ id: "auth-components", source: value.componentDir, order: "normal" as const }]
        : [];
    },

    routeEntries(context) {
      const value = resolved(context, options);

      if (!value.enabled) {
        return [];
      }

      return AUTH_ROUTE_DEFINITIONS.filter((route) => routeEnabled(value.routes, route.group)).map(
        ({ path }) => ({
          kind: "api" as const,
          path,
          entry: authApiRouteEntry(path),
        }),
      );
    },

    middleware(context) {
      const value = resolved(context, options);
      return value.enabled && value.middleware ? [middlewareFile] : [];
    },

    migrations(context) {
      const value = resolved(context, options);
      return value.enabled && value.migrations
        ? [
            {
              id: "wrnexus-auth-001",
              source: readFileSync(migrationsFile, "utf8"),
            },
            {
              id: "wrnexus-auth-002-otp-purpose",
              source: readFileSync(otpPurposeMigrationFile, "utf8"),
            },
          ]
        : [];
    },

    configure(config, context) {
      const value = resolveConfig(config, options);

      context.metadata.set(resolvedConfigKey, value);

      config.auth = {
        ...((config.auth ?? {}) as Record<string, unknown>),

        componentDir: value.componentDir,

        schemas: value.schemas,
      };

      if (value.engine) {
        setDefaultAuthEngine(value.engine);
      } else {
        clearDefaultAuthEngine();
      }

      setDefaultAuthSchemas(value.schemas);

      setDefaultAuthRouteOptions({
        baseUrl: value.baseUrl,

        csrf: value.csrf,

        passkey: value.passkey,

        onSignedIn: value.onSignedIn,

        onSignedOut: value.onSignedOut,

        sessionCookieName: value.sessionCookieName,

        onSessionVerification: value.onSessionVerification,
      });

      context.metadata.set("@wrnexus/auth:component-dir", value.componentDir);

      context.metadata.set("@wrnexus/auth:configured", Boolean(value.engine));
    },

    transformCode(code, context) {
      if (context.mode !== "development") return;
      const previous = (context.metadata.get(metadataKey) as AuthAuditIssue[] | undefined) ?? [];
      context.metadata.set(metadataKey, [
        ...previous.filter((issue) => issue.file !== context.file),
        ...audit(code, context.file),
      ]);
    },

    devToolbarPanels(context) {
      const value = resolved(context, options);
      if (!value.enabled || !value.devToolbar) return [];
      const issues = (context.metadata.get(metadataKey) as AuthAuditIssue[] | undefined) ?? [];
      return [
        {
          id: "wrnexus-auth",
          title: "Authentication",
          icon: "shield-user",
          badge: issues.length,
          description: "Authentication security, session, passkey, and recovery checks",
          issues,
        },
      ];
    },
  });
}

export default authPlugin;
