import { ConfidentialClientApplication, LogLevel } from '@azure/msal-node';
import { Module } from '@nestjs/common';
import { Logger } from 'nestjs-pino';
import { assertEntraConfig } from '../config/check-entra-config';
import { loadEntraGroupToRoleResolver } from '../config/load-entra-group-map';
import { SessionModule } from '../session/session.module';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { ENTRA_CONFIG, type EntraConfig } from './entra-config.token';
import { ENTRA_GROUP_TO_ROLE_RESOLVER } from './entra-group-to-role.token';
import { MSAL_CLIENT } from './msal-client.token';
import { PrincipalBuilder } from './principal-builder';
import { PrismaScopeResolver } from './prisma-scope-resolver';
import { RequireMfaGuard } from './require-mfa.guard';
import { RequirePrivilegeGuard } from './require-privilege.guard';
import { RequireRoleGuard } from './require-role.guard';
import { RequireScopeGuard } from './require-scope.guard';
import { ScopeResolver } from './scope-resolver';
import { SessionEstablisher } from './session-establisher.service';

/**
 * Auth module — owns the Entra ID configuration, the MSAL Node
 * confidential client, and (in subsequent PRs) the OIDC routes, the
 * session integration, and the route guards. Per ADR-0009.
 *
 * v1 providers:
 *
 *   - `ENTRA_CONFIG` — the parsed, validated Entra app-registration
 *     config (PR #102).
 *   - `MSAL_CLIENT` — a `ConfidentialClientApplication` instance
 *     wired with that config plus a logger callback that forwards
 *     MSAL's internal log lines into the Pino stream so auth flow
 *     diagnostics land alongside the rest of the BFF logs (per
 *     ADR-0012). PII logging is disabled by default — MSAL won't
 *     include tokens or user identifiers in the messages it emits.
 *   - `AuthService` — first-leg-of-the-flow logic: PKCE + state
 *     generation, MSAL auth-code URL building. The controller
 *     stays a thin shell around it.
 *
 * v1 routes (per ADR-0009):
 *
 *   - `GET /api/auth/login` — 302 to Entra's authorize endpoint
 *     with the freshly-generated state + code challenge; sets a
 *     short-lived signed cookie carrying the state + PKCE
 *     verifier so the next-PR callback can verify the round-trip.
 *
 * The module stays non-global: modules state "I depend on auth" by
 * importing it. Re-exports both tokens so a single
 * `imports: [AuthModule]` is enough to consume either.
 */
@Module({
  imports: [SessionModule],
  controllers: [AuthController],
  providers: [
    AuthService,
    RequireMfaGuard,
    RequirePrivilegeGuard,
    RequireRoleGuard,
    RequireScopeGuard,
    SessionEstablisher,
    PrincipalBuilder,
    // PrismaScopeResolver replaces StubScopeResolver per ADR-0026 PR 2.
    // The stub class stays exported from scope-resolver.ts as a
    // fallback for spec fixtures / future "force-unrestricted" dev
    // modes, but it is no longer the default wiring.
    { provide: ScopeResolver, useClass: PrismaScopeResolver },
    {
      provide: ENTRA_CONFIG,
      useFactory: () => assertEntraConfig(),
    },
    {
      provide: ENTRA_GROUP_TO_ROLE_RESOLVER,
      inject: [Logger],
      useFactory: (logger: Logger) => {
        const { resolver, sourcePath } = loadEntraGroupToRoleResolver({
          onWarn: (event, payload) => logger.warn({ event, ...payload }, 'AuthModule'),
        });
        // Log the post-load summary unconditionally so an operator
        // grepping the boot log can confirm how many functional
        // roles are wired without inspecting the JSON file. The
        // file path (if any) helps diagnose env-var / cwd mismatches.
        logger.log(
          {
            event: 'auth.entra_group_map_loaded',
            sourcePath,
            mappingCount: resolver.size,
            coveredRoles: resolver.coveredRoles(),
          },
          'AuthModule',
        );
        return resolver;
      },
    },
    {
      provide: MSAL_CLIENT,
      inject: [ENTRA_CONFIG, Logger],
      useFactory: (config: EntraConfig, logger: Logger) =>
        new ConfidentialClientApplication({
          auth: {
            clientId: config.clientId,
            authority: config.authority,
            clientSecret: config.clientSecret,
          },
          system: {
            loggerOptions: {
              piiLoggingEnabled: false,
              logLevel: LogLevel.Info,
              loggerCallback: (level, message) => {
                // MSAL levels: 0=Error, 1=Warning, 2=Info, 3=Verbose, 4=Trace.
                // Forward to Pino with the matching level. The whole
                // payload lives under the `msal` context key so a log
                // search filtering on `msal` returns every MSAL line
                // without false positives from app code.
                const ctx = 'msal';
                switch (level) {
                  case LogLevel.Error:
                    logger.error(message, ctx);
                    break;
                  case LogLevel.Warning:
                    logger.warn(message, ctx);
                    break;
                  case LogLevel.Verbose:
                  case LogLevel.Trace:
                    logger.debug(message, ctx);
                    break;
                  default:
                    logger.log(message, ctx);
                }
              },
            },
          },
        }),
    },
  ],
  exports: [
    ENTRA_CONFIG,
    ENTRA_GROUP_TO_ROLE_RESOLVER,
    MSAL_CLIENT,
    RequireMfaGuard,
    RequirePrivilegeGuard,
    RequireRoleGuard,
    RequireScopeGuard,
    AuthService,
    PrincipalBuilder,
    ScopeResolver,
    SessionEstablisher,
  ],
})
export class AuthModule {}
