import { NestFactory } from '@nestjs/core';
import { ValidationPipe, RequestMethod } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import type { Request, Response, NextFunction } from 'express';
import helmet from 'helmet';
import cookieParser from 'cookie-parser';
import * as bodyParser from 'body-parser';
import { AppModule } from './app.module';
import { MetricsService } from './metrics/metrics.service';
import { getLogger } from './common/logger';

const logger = getLogger('Bootstrap');

// Normalize route paths to prevent high cardinality
function normalizeRoute(path: string): string {
	return path
		.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, ':id')
		.replace(/\/\d+/g, '/:id');
}

async function bootstrap() {
	const app = await NestFactory.create(AppModule, {
		rawBody: true, // Enable raw body for Stripe webhook signature verification
	});

	const configService = app.get(ConfigService);

	// Get MetricsService for request tracking
	const metricsService = app.get(MetricsService);

	// Global Express middleware to track ALL HTTP requests
	app.use((req: Request, res: Response, next: NextFunction) => {
		if (req.path === '/metrics') {
			return next();
		}

		const startTime = Date.now();
		const method = req.method;
		const route = normalizeRoute(req.path);

		res.once('finish', () => {
			const duration = (Date.now() - startTime) / 1000;
			metricsService.httpRequestsTotal.inc({
				method,
				route,
				status: res.statusCode.toString(),
			});
			metricsService.httpRequestDuration.observe(
				{ method, route, status: res.statusCode.toString() },
				duration
			);
		});

		next();
	});

	// Security middleware - configure helmet to allow CORS and inline scripts for login page
	app.use(
		helmet({
			crossOriginResourcePolicy: { policy: 'cross-origin' },
			crossOriginOpenerPolicy: { policy: 'same-origin-allow-popups' },
			contentSecurityPolicy: {
				directives: {
					defaultSrc: ["'self'"],
					scriptSrc: ["'self'", "'unsafe-inline'"], // Allow inline scripts for login page
					styleSrc: ["'self'", "'unsafe-inline'"], // Allow inline styles
					imgSrc: ["'self'", 'data:', 'https:'],
					connectSrc: ["'self'"],
					fontSrc: ["'self'"],
					objectSrc: ["'none'"],
					frameAncestors: ["'none'"],
				},
			},
		})
	);
	app.use(cookieParser());

	// Body parser for form-urlencoded (needed for OAuth2 token endpoint)
	// Note: JSON body parsing is handled by NestJS internally (rawBody: true mode)
	// DO NOT add bodyParser.json() here - it conflicts with NestJS rawBody mode
	// and causes "stream is not readable" errors
	app.use(bodyParser.urlencoded({ extended: true }));

	// CORS configuration
	const corsOrigins = configService.get<string[]>('cors.origin') || [];
	logger.info('CORS Origins configured', { origins: corsOrigins });
	app.enableCors({
		origin: corsOrigins,
		credentials: true,
		methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
		allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'X-App-Id'],
	});

	// Global validation pipe
	app.useGlobalPipes(
		new ValidationPipe({
			whitelist: true,
			forbidNonWhitelisted: true,
			transform: true,
			transformOptions: {
				enableImplicitConversion: true,
			},
		})
	);

	// Global prefix (exclude metrics, health, Better Auth native routes, and OIDC routes)
	// Better Auth generates verification URLs with /api/auth/* prefix
	// OIDC Provider requires routes without prefix: /.well-known/*, /api/auth/oauth2/*, /api/oidc/*
	app.setGlobalPrefix('api/v1', {
		exclude: [
			{ path: 'metrics', method: RequestMethod.ALL },
			{ path: 'health', method: RequestMethod.ALL },
			// OIDC login page
			{ path: 'login', method: RequestMethod.ALL },
			// Better Auth routes (verification emails, password reset, sign-in, SSO)
			{ path: 'api/auth/get-session', method: RequestMethod.ALL },
			{ path: 'api/auth/verify-email', method: RequestMethod.ALL },
			{ path: 'api/auth/reset-password/(.*)', method: RequestMethod.ALL },
			{ path: 'api/auth/sign-in/(.*)', method: RequestMethod.ALL },
			// Better Auth OIDC/OAuth2 routes (native paths from discovery document)
			{ path: 'api/auth/jwks', method: RequestMethod.ALL },
			{ path: 'api/auth/oauth2/(.*)', method: RequestMethod.ALL },
			{ path: 'api/auth/oauth2/authorize', method: RequestMethod.ALL },
			{ path: 'api/auth/oauth2/token', method: RequestMethod.ALL },
			{ path: 'api/auth/oauth2/userinfo', method: RequestMethod.ALL },
			{ path: 'api/auth/oauth2/:path*', method: RequestMethod.ALL },
			// OIDC discovery
			{ path: '.well-known/(.*)', method: RequestMethod.ALL },
			{ path: '.well-known/openid-configuration', method: RequestMethod.ALL },
			// Alternative OIDC routes
			{ path: 'api/oidc/(.*)', method: RequestMethod.ALL },
			{ path: 'api/oidc/:path*', method: RequestMethod.ALL },
		],
	});

	// Swagger/OpenAPI documentation
	const swaggerConfig = new DocumentBuilder()
		.setTitle('Mana Core Auth API')
		.setDescription(
			`
## Authentication & Authorization Service

Mana Core Auth provides centralized authentication for the Mana ecosystem.

### Features
- **User Authentication**: Registration, login, password reset
- **JWT Tokens**: EdDSA-signed access tokens via JWKS
- **Organizations (B2B)**: Multi-tenant support with roles
- **Credits**: Usage-based credit system
- **OIDC Provider**: OAuth2/OpenID Connect for SSO

### Authentication
Most endpoints require a Bearer token in the Authorization header:
\`\`\`
Authorization: Bearer <access_token>
\`\`\`

### Rate Limits
- Registration: 5 req/min
- Login: 10 req/min
- Password Reset: 3 req/min
`
		)
		.setVersion('1.0')
		.addBearerAuth(
			{
				type: 'http',
				scheme: 'bearer',
				bearerFormat: 'JWT',
				description: 'Enter your JWT access token',
			},
			'JWT-auth'
		)
		.addTag('auth', 'User authentication (login, register, logout)')
		.addTag('organizations', 'B2B organization management')
		.addTag('credits', 'Credit balance and transactions')
		.addTag('health', 'Service health checks')
		.addServer('http://localhost:3001', 'Local Development')
		.addServer('https://auth.mana.how', 'Production')
		.build();

	const document = SwaggerModule.createDocument(app, swaggerConfig);
	SwaggerModule.setup('api-docs', app, document, {
		swaggerOptions: {
			persistAuthorization: true,
			tagsSorter: 'alpha',
			operationsSorter: 'alpha',
		},
		customSiteTitle: 'Mana Core Auth API',
	});

	const port = configService.get<number>('port') || 3001;
	await app.listen(port);

	logger.info(`Mana Core Auth running on http://localhost:${port}`, {
		port,
		environment: configService.get<string>('nodeEnv'),
		docs: `http://localhost:${port}/api-docs`,
	});
}

bootstrap();
