/**
 * @ai-summary Fastify JWT authentication plugin using Auth0
 * @ai-context Validates JWT tokens against Auth0 JWKS endpoint, hydrates userContext with profile
 * @ai-context Includes email verification guard to block unverified users
 */
import { FastifyPluginAsync, FastifyRequest, FastifyReply } from 'fastify';
import fp from 'fastify-plugin';
import buildGetJwks from 'get-jwks';
import fastifyJwt from '@fastify/jwt';
import { appConfig } from '../config/config-loader';
import { logger } from '../logging/logger';
import { UserProfileRepository } from '../../features/user-profile/data/user-profile.repository';
import { pool } from '../config/database';
import { auth0ManagementClient } from '../auth/auth0-management.client';
import { SubscriptionTier } from '../../features/user-profile/domain/user-profile.types';

// Routes that don't require email verification
const VERIFICATION_EXEMPT_ROUTES = [
  '/api/auth/',
  '/api/onboarding/',
  '/api/health',
  '/health',
];

// Define the Auth0 JWT payload type
interface Auth0JwtPayload {
  sub: string;
  email?: string;
  name?: string;
  nickname?: string;
  'https://motovaultpro.com/roles'?: string[];
  iss?: string;
  aud?: string | string[];
  iat?: number;
  exp?: number;
}

// Extend @fastify/jwt module with our payload type
declare module '@fastify/jwt' {
  interface FastifyJWT {
    payload: Auth0JwtPayload;
    user: Auth0JwtPayload;
  }
}

declare module 'fastify' {
  interface FastifyInstance {
    authenticate: (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
  }
  interface FastifyRequest {
    jwtVerify(): Promise<void>;
    userContext?: {
      userId: string;
      email?: string;
      displayName?: string;
      emailVerified: boolean;
      onboardingCompleted: boolean;
      isAdmin: boolean;
      adminRecord?: any;
      subscriptionTier: SubscriptionTier;
    };
  }
}

const authPlugin: FastifyPluginAsync = async (fastify) => {
  const auth0Config = appConfig.getAuth0Config();

  // Security validation: ensure AUTH0_DOMAIN is properly configured
  if (!auth0Config.domain || !auth0Config.domain.includes('.auth0.com')) {
    throw new Error('AUTH0_DOMAIN must be a valid Auth0 domain');
  }

  // Initialize JWKS client for Auth0 public key retrieval
  const getJwks = buildGetJwks({
    ttl: 60 * 60 * 1000, // 1 hour cache
  });

  // Register @fastify/jwt with Auth0 JWKS validation
  await fastify.register(fastifyJwt, {
    decode: { complete: true },
    secret: async (_request: FastifyRequest, token: any) => {
      try {
        const { header: { kid, alg }, payload: { iss } } = token;
        
        // Validate issuer matches Auth0 domain (security: prevent issuer spoofing)
        const expectedIssuer = `https://${auth0Config.domain}/`;
        if (iss !== expectedIssuer) {
          throw new Error(`Invalid issuer: ${iss}`);
        }

        // Get public key from Auth0 JWKS endpoint (security: uses full HTTPS URL)
        return getJwks.getPublicKey({ 
          kid, 
          domain: expectedIssuer, // Use validated issuer as domain 
          alg 
        });
      } catch (error) {
        logger.error('JWKS key retrieval failed', {
          error: error instanceof Error ? error.message : 'Unknown error',
          domain: auth0Config.domain
        });
        throw error;
      }
    },
    verify: {
      allowedIss: `https://${auth0Config.domain}/`,
      allowedAud: auth0Config.audience,
    },
  });

  // Initialize profile repository for user profile hydration
  const profileRepo = new UserProfileRepository(pool);

  // Helper to check if route is exempt from verification
  const isVerificationExempt = (url: string): boolean => {
    return VERIFICATION_EXEMPT_ROUTES.some(route => url.startsWith(route));
  };

  // Decorate with authenticate function that validates JWT
  fastify.decorate('authenticate', async function(request: FastifyRequest, reply: FastifyReply) {
    try {
      await request.jwtVerify();

      const userId = request.user?.sub;
      if (!userId) {
        throw new Error('Missing user ID in JWT');
      }

      // Get or create user profile from database
      let email = request.user?.email;
      let displayName: string | undefined;
      let emailVerified = false;
      let onboardingCompleted = false;
      let subscriptionTier: SubscriptionTier = 'free';

      try {
        // If JWT doesn't have email, fetch from Auth0 Management API
        if (!email || email.includes('@unknown.local')) {
          try {
            const auth0User = await auth0ManagementClient.getUser(userId);
            if (auth0User.email) {
              email = auth0User.email;
              emailVerified = auth0User.emailVerified;
              logger.info('Fetched email from Auth0 Management API', {
                userId: userId.substring(0, 8) + '...',
                hasEmail: true,
              });
            }
          } catch (auth0Error) {
            logger.warn('Failed to fetch user from Auth0 Management API', {
              userId: userId.substring(0, 8) + '...',
              error: auth0Error instanceof Error ? auth0Error.message : 'Unknown error',
            });
          }
        }

        // Get or create profile with correct email
        const profile = await profileRepo.getOrCreate(userId, {
          email: email || `${userId}@unknown.local`,
          displayName: request.user?.name || request.user?.nickname,
        });

        // If profile has placeholder email but we now have real email, update it
        if (profile.email.includes('@unknown.local') && email && !email.includes('@unknown.local')) {
          await profileRepo.updateEmail(userId, email);
          logger.info('Updated profile with correct email from Auth0', {
            userId: userId.substring(0, 8) + '...',
          });
        }

        // Use notificationEmail if set, otherwise fall back to profile email
        email = profile.notificationEmail || profile.email;
        displayName = profile.displayName || undefined;
        emailVerified = profile.emailVerified;
        onboardingCompleted = profile.onboardingCompletedAt !== null;
        subscriptionTier = profile.subscriptionTier || 'free';

        // Sync email verification status from Auth0 if needed
        if (!emailVerified) {
          try {
            const isVerifiedInAuth0 = await auth0ManagementClient.checkEmailVerified(userId);
            if (isVerifiedInAuth0 && !profile.emailVerified) {
              await profileRepo.updateEmailVerified(userId, true);
              emailVerified = true;
              logger.info('Synced email verification status from Auth0', {
                userId: userId.substring(0, 8) + '...',
              });
            }
          } catch (syncError) {
            // Don't fail auth if sync fails, just log
            logger.warn('Failed to sync email verification status', {
              userId: userId.substring(0, 8) + '...',
              error: syncError instanceof Error ? syncError.message : 'Unknown error',
            });
          }
        }
      } catch (profileError) {
        // Log but don't fail auth if profile fetch fails
        logger.warn('Failed to fetch user profile', {
          userId: userId.substring(0, 8) + '...',
          error: profileError instanceof Error ? profileError.message : 'Unknown error',
        });
        // Fall back to JWT email if available
        email = request.user?.email;
      }

      // Hydrate userContext with profile data
      request.userContext = {
        userId,
        email,
        displayName,
        emailVerified,
        onboardingCompleted,
        isAdmin: false, // Default to false; admin status checked by admin guard
        subscriptionTier,
      };

      // Email verification guard - block unverified users from non-exempt routes
      if (!emailVerified && !isVerificationExempt(request.url)) {
        logger.warn('Blocked unverified user from accessing protected route', {
          userId: userId.substring(0, 8) + '...',
          path: request.url,
        });
        return reply.code(403).send({
          error: 'Email not verified',
          message: 'Please verify your email address before accessing the application',
          code: 'EMAIL_NOT_VERIFIED',
        });
      }

      logger.info('JWT authentication successful', {
        userId: userId.substring(0, 8) + '...',
        hasEmail: !!email,
        emailVerified,
        audience: auth0Config.audience,
      });
    } catch (error) {
      logger.warn('JWT authentication failed', {
        path: request.url,
        method: request.method,
        error: error instanceof Error ? error.message : 'Unknown error',
      });

      return reply.code(401).send({
        error: 'Unauthorized',
        message: 'Invalid or missing JWT token',
      });
    }
  });
};

export default fp(authPlugin, {
  name: 'auth-plugin'
});