import { INestApplication, ValidationPipe, VersioningType } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { NestFactory } from '@nestjs/core';
import { JwtService } from '@nestjs/jwt';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { UserRole } from '@prisma/client';
import { NextFunction, Request, Response } from 'express';
import helmet from 'helmet';
import { AppModule } from './app.module';
import { apiPrefix, apiVersion, consoleLoginPath, docsJsonPath, docsPath, docsRoute, docsYamlPath, sandboxPath, versionedApiPath } from './common/api-paths';

const CONSOLE_COOKIE_NAME = 'smart_estate_console_token';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const config = app.get(ConfigService);
  const jwt = app.get(JwtService);

  app.use(
    helmet({
      contentSecurityPolicy: {
        directives: {
          defaultSrc: ["'self'"],
          baseUri: ["'self'"],
          fontSrc: ["'self'", 'https:', 'data:'],
          formAction: ["'self'"],
          frameAncestors: ["'self'"],
          imgSrc: ["'self'", 'data:', 'validator.swagger.io'],
          objectSrc: ["'none'"],
          scriptSrc: ["'self'", "'unsafe-inline'"],
          scriptSrcAttr: ["'none'"],
          styleSrc: ["'self'", 'https:', "'unsafe-inline'"],
          connectSrc: ["'self'", 'http://localhost:*', 'http://127.0.0.1:*'],
          upgradeInsecureRequests: null
        }
      }
    })
  );
  app.enableCors({
    origin: parseCorsOrigins(config.get<string>('CORS_ORIGINS')),
    credentials: true
  });
  const globalPrefix = apiPrefix(config);
  if (globalPrefix) {
    app.setGlobalPrefix(globalPrefix);
  }
  app.enableVersioning({
    type: VersioningType.URI,
    defaultVersion: apiVersion(config)
  });
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      transform: true,
      forbidNonWhitelisted: true
    })
  );
  protectConsoleRoutes(app, jwt, config);

  const swaggerBuilder = new DocumentBuilder()
    .setTitle('Smart Estate API')
    .setDescription('Multi-tenant estate access and visitor management platform API')
    .setVersion('1.0')
    .addServer(versionedApiPath(config), 'Current host API base')
    .addBearerAuth(
      {
        type: 'http',
        scheme: 'bearer',
        bearerFormat: 'JWT',
        description: 'JWT Bearer token for authentication'
      },
      'bearer'
    )
    .addTag('Auth', 'Authentication endpoints (OTP-based)')
    .addTag('Residents', 'Resident management endpoints')
    .addTag('Guards', 'Guard management endpoints')
    .addTag('Visitors', 'Visitor and visitor pass management')
    .addTag('Checkin', 'Visitor check-in and check-out')
    .addTag('Incidents', 'Incident reporting and tracking')
    .addTag('Offline', 'Offline sync and data bootstrap')
    .addTag('Estates', 'Estate management (super admin only)')
    .addTag('Notifications', 'Push notification endpoints')
    .addTag('Admin', 'Super admin dashboard and control endpoints')
    .addTag('Onboarding', 'Public estate admin signup, plan selection, hosted sandbox checkout, and payment callback capture')
    .addTag('Sandbox', 'Super admin browser sandbox for trying API requests');

  const publicApiBase = publicApiBaseUrl(config);
  if (publicApiBase) {
    swaggerBuilder.addServer(`${publicApiBase}/v${apiVersion(config)}`, 'Configured public API base');
  }

  const swaggerConfig = swaggerBuilder.build();

  const swaggerDocument = SwaggerModule.createDocument(app, swaggerConfig);
  SwaggerModule.setup(docsRoute(config), app, swaggerDocument, {
    swaggerOptions: {
      persistAuthorization: true
    }
  });

  const port = resolvePort(config);
  await app.listen(port);
  console.log(`API listening on http://localhost:${port}${versionedApiPath(config)}`);
  console.log(`Super admin docs available at http://localhost:${port}${docsPath(config)}`);
}

function protectConsoleRoutes(app: INestApplication, jwt: JwtService, config: ConfigService) {
  app.use(async (req: Request, res: Response, next: NextFunction) => {
    if (!isProtectedConsolePath(req.path, config)) {
      return next();
    }

    const token = extractBearerToken(req.headers.authorization) ?? parseCookie(req.headers.cookie)[CONSOLE_COOKIE_NAME];
    if (token && (await isSuperAdminToken(token, jwt, config))) {
      return next();
    }

    if (req.method === 'GET' && acceptsHtml(req)) {
      return res.redirect(`${consoleLoginPath(config)}?returnTo=${encodeURIComponent(req.originalUrl || req.url)}`);
    }

    return res.status(401).json({
      statusCode: 401,
      message: 'Super admin console login required',
      error: 'Unauthorized'
    });
  });
}

function isProtectedConsolePath(path: string, config: ConfigService) {
  const docs = docsPath(config);
  const docsJson = docsJsonPath(config);
  const docsYaml = docsYamlPath(config);
  const sandbox = sandboxPath(config);
  return (
    path === docs ||
    path.startsWith(`${docs}/`) ||
    path === docsJson ||
    path === docsYaml ||
    path === sandbox ||
    path.startsWith(`${sandbox}/`)
  );
}

async function isSuperAdminToken(token: string, jwt: JwtService, config: ConfigService) {
  try {
    const payload = await jwt.verifyAsync<{ role: UserRole }>(token, {
      secret: config.get('JWT_ACCESS_SECRET')
    });
    return payload.role === UserRole.SUPER_ADMIN;
  } catch {
    return false;
  }
}

function extractBearerToken(authorization?: string | string[]) {
  const value = Array.isArray(authorization) ? authorization[0] : authorization;
  const match = value?.trim().match(/^Bearer\s+(.+)$/i);
  return match?.[1]?.trim();
}

function parseCookie(cookieHeader?: string) {
  return Object.fromEntries(
    (cookieHeader ?? '')
      .split(';')
      .map((part) => part.trim())
      .filter(Boolean)
      .map((part) => {
        const index = part.indexOf('=');
        if (index === -1) {
          return [part, ''];
        }
        return [part.slice(0, index), decodeURIComponent(part.slice(index + 1))];
      })
  );
}

function acceptsHtml(req: Request) {
  return req.accepts(['html', 'json']) === 'html';
}

function resolvePort(config: ConfigService) {
  return Number(config.get<string>('PORT') ?? config.get<string>('API_PORT') ?? 4000);
}

function parseCorsOrigins(value?: string) {
  if (!value || value.trim() === '*') {
    return true;
  }
  return value
    .split(',')
    .map((origin) => origin.trim())
    .filter(Boolean);
}

function publicApiBaseUrl(config: ConfigService) {
  const configured = config.get<string>('PUBLIC_API_BASE_URL')?.trim();
  return configured ? configured.replace(/\/+$/, '') : null;
}

void bootstrap();
