import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import { createError } from './errorHandler';
import logger from '../utils/logger';
import { prisma } from '../index';

type UserRole = 'ADMIN' | 'TEACHER' | 'STUDENT';

export interface AuthenticatedRequest extends Request {
  user?: {
    id: string;
    email: string;
    role: UserRole;
    name: string;
    canCreateBots: boolean;
    canEditBots: boolean;
  };
}

export interface JWTPayload {
  userId: string;
  email: string;
  role: UserRole;
  iat?: number;
  exp?: number;
}

export const authenticate = async (
  req: AuthenticatedRequest,
  res: Response,
  next: NextFunction
) => {
  const startTime = Date.now();
  
  logger.info('🔐 Authentication attempt', {
    path: req.path,
    method: req.method,
    ip: req.ip,
    userAgent: req.get('User-Agent'),
    origin: req.get('Origin'),
    hasAuthHeader: !!req.headers.authorization,
    hasCookies: !!req.cookies.accessToken
  });

  try {
    let token: string | undefined;

    // Check for token in Authorization header
    const authHeader = req.headers.authorization;
    if (authHeader && authHeader.startsWith('Bearer ')) {
      token = authHeader.substring(7);
      logger.info('🎫 Token found in Authorization header', {
        tokenLength: token.length,
        path: req.path
      });
    }

    // Check for token in cookies
    if (!token && req.cookies.accessToken) {
      token = req.cookies.accessToken;
      logger.info('🍪 Token found in cookies', {
        tokenLength: token.length,
        path: req.path
      });
    }

    if (!token) {
      logger.warn('❌ Authentication failed - no token provided', {
        path: req.path,
        ip: req.ip,
        userAgent: req.get('User-Agent')
      });
      throw createError('Access token is required', 401);
    }

    logger.info('🔍 Verifying JWT token', {
      tokenLength: token.length,
      path: req.path
    });

    // Verify token
    const jwtSecret = process.env.JWT_SECRET;
    if (!jwtSecret) {
      logger.error('❌ JWT_SECRET is not configured', {
        path: req.path,
        ip: req.ip
      });
      throw createError('Server configuration error', 500);
    }

    const decoded = jwt.verify(
      token,
      jwtSecret
    ) as JWTPayload;

    logger.info('✅ JWT token verified successfully', {
      userId: decoded.userId,
      email: decoded.email,
      role: decoded.role,
      issuedAt: decoded.iat ? new Date(decoded.iat * 1000).toISOString() : 'unknown',
      expiresAt: decoded.exp ? new Date(decoded.exp * 1000).toISOString() : 'unknown',
      path: req.path
    });

    // Get user from database
    logger.info('🗄️ Querying database for user', {
      userId: decoded.userId,
      path: req.path
    });

    const user = await prisma.user.findUnique({
      where: { id: decoded.userId },
      select: {
        id: true,
        email: true,
        name: true,
        role: true,
        canCreateBots: true,
        canEditBots: true,
      },
    });

    if (!user) {
      logger.warn('❌ Authentication failed - user not found in database', {
        userId: decoded.userId,
        email: decoded.email,
        path: req.path,
        ip: req.ip
      });
      throw createError('User not found', 401);
    }

    logger.info('✅ User found in database', {
      userId: user.id,
      email: user.email,
      name: user.name,
      role: user.role,
      path: req.path
    });

    // Attach user to request
    req.user = user;
    
    const totalTime = Date.now() - startTime;
    
    logger.info('🎉 Authentication successful', {
      userId: user.id,
      email: user.email,
      name: user.name,
      role: user.role,
      path: req.path,
      totalTime: `${totalTime}ms`
    });
    
    next();
  } catch (error) {
    const totalTime = Date.now() - startTime;
    
    if (error instanceof jwt.JsonWebTokenError) {
      logger.warn('❌ Invalid JWT token', { 
        error: error.message,
        path: req.path,
        ip: req.ip,
        userAgent: req.get('User-Agent'),
        totalTime: `${totalTime}ms`
      });
      return next(createError('Invalid token', 401));
    }
    if (error instanceof jwt.TokenExpiredError) {
      logger.warn('❌ Expired JWT token', { 
        error: error.message,
        path: req.path,
        ip: req.ip,
        userAgent: req.get('User-Agent'),
        totalTime: `${totalTime}ms`
      });
      return next(createError('Token expired', 401));
    }
    
    logger.error('❌ Authentication error', { 
      error: error.message,
      path: req.path,
      ip: req.ip,
      userAgent: req.get('User-Agent'),
      totalTime: `${totalTime}ms`,
      stack: error.stack
    });
    next(error);
  }
};

export const authorize = (...roles: UserRole[]) => {
  return (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
    const startTime = Date.now();
    
    logger.info('🔒 Authorization check', {
      path: req.path,
      method: req.method,
      requiredRoles: roles,
      userId: req.user?.id,
      userRole: req.user?.role,
      ip: req.ip
    });

    if (!req.user) {
      logger.warn('❌ Authorization failed - no authenticated user', {
        path: req.path,
        method: req.method,
        requiredRoles: roles,
        ip: req.ip
      });
      return next(createError('Authentication required', 401));
    }

    if (!roles.includes(req.user.role)) {
      const totalTime = Date.now() - startTime;
      
      logger.warn('❌ Authorization failed - insufficient permissions', {
        userId: req.user.id,
        userRole: req.user.role,
        requiredRoles: roles,
        path: req.path,
        method: req.method,
        ip: req.ip,
        totalTime: `${totalTime}ms`
      });
      return next(createError('Insufficient permissions', 403));
    }

    const totalTime = Date.now() - startTime;
    
    logger.info('✅ Authorization successful', {
      userId: req.user.id,
      userRole: req.user.role,
      requiredRoles: roles,
      path: req.path,
      method: req.method,
      totalTime: `${totalTime}ms`
    });

    next();
  };
};

// Middleware to check if user is admin
export const requireAdmin = authorize('ADMIN');

// Middleware to check if user is teacher or admin
export const requireTeacherOrAdmin = authorize('TEACHER', 'ADMIN');

// Middleware to check if user is student, teacher, or admin
export const requireAuthenticated = authorize('STUDENT', 'TEACHER', 'ADMIN');

// Middleware to check a per-teacher bot permission flag (ADMIN always passes)
export const requireBotPermission = (flag: 'canCreateBots' | 'canEditBots') => {
  return (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
    if (!req.user) {
      return next(createError('Authentication required', 401));
    }

    if (req.user.role === 'ADMIN') {
      return next();
    }

    if (req.user.role === 'TEACHER' && req.user[flag]) {
      return next();
    }

    logger.warn('❌ Authorization failed - missing bot permission', {
      userId: req.user.id,
      userRole: req.user.role,
      requiredFlag: flag,
      path: req.path,
      method: req.method,
    });

    return next(createError('Insufficient permissions', 403));
  };
};

// Optional authentication - doesn't throw error if no token
export const optionalAuth = async (
  req: AuthenticatedRequest,
  res: Response,
  next: NextFunction
) => {
  const startTime = Date.now();
  
  logger.info('🔍 Optional authentication check', {
    path: req.path,
    method: req.method,
    ip: req.ip,
    userAgent: req.get('User-Agent'),
    hasAuthHeader: !!req.headers.authorization,
    hasCookies: !!req.cookies.accessToken
  });

  try {
    let token: string | undefined;

    const authHeader = req.headers.authorization;
    if (authHeader && authHeader.startsWith('Bearer ')) {
      token = authHeader.substring(7);
      logger.info('🎫 Token found in Authorization header (optional)', {
        tokenLength: token.length,
        path: req.path
      });
    }

    if (!token && req.cookies.accessToken) {
      token = req.cookies.accessToken;
      logger.info('🍪 Token found in cookies (optional)', {
        tokenLength: token.length,
        path: req.path
      });
    }

    if (token) {
      logger.info('🔍 Verifying optional JWT token', {
        tokenLength: token.length,
        path: req.path
      });

      const jwtSecret = process.env.JWT_SECRET;
      if (!jwtSecret) {
        logger.error('❌ JWT_SECRET is not configured (optional auth)', {
          path: req.path
        });
        // For optional auth, just continue without authenticating
        return next();
      }

      const decoded = jwt.verify(
        token,
        jwtSecret
      ) as JWTPayload;

      logger.info('✅ Optional JWT token verified', {
        userId: decoded.userId,
        email: decoded.email,
        role: decoded.role,
        path: req.path
      });

      const user = await prisma.user.findUnique({
        where: { id: decoded.userId },
        select: {
          id: true,
          email: true,
          name: true,
          role: true,
          canCreateBots: true,
          canEditBots: true,
        },
      });

      if (user) {
        req.user = user;
        
        const totalTime = Date.now() - startTime;
        
        logger.info('✅ Optional authentication successful', {
          userId: user.id,
          email: user.email,
          name: user.name,
          role: user.role,
          path: req.path,
          totalTime: `${totalTime}ms`
        });
      } else {
        logger.warn('⚠️ Optional authentication - user not found in database', {
          userId: decoded.userId,
          email: decoded.email,
          path: req.path
        });
      }
    } else {
      logger.info('ℹ️ No token provided for optional authentication', {
        path: req.path
      });
    }

    const totalTime = Date.now() - startTime;
    
    logger.info('✅ Optional authentication check completed', {
      hasUser: !!req.user,
      userId: req.user?.id,
      userRole: req.user?.role,
      path: req.path,
      totalTime: `${totalTime}ms`
    });

    next();
  } catch (error) {
    const totalTime = Date.now() - startTime;
    
    // For optional auth, we don't throw errors, just log them
    if (error instanceof jwt.JsonWebTokenError) {
      logger.warn('⚠️ Optional authentication - invalid JWT token', { 
        error: error.message,
        path: req.path,
        totalTime: `${totalTime}ms`
      });
    } else if (error instanceof jwt.TokenExpiredError) {
      logger.warn('⚠️ Optional authentication - expired JWT token', { 
        error: error.message,
        path: req.path,
        totalTime: `${totalTime}ms`
      });
    } else {
      logger.error('❌ Optional authentication error', { 
        error: error.message,
        path: req.path,
        totalTime: `${totalTime}ms`,
        stack: error.stack
      });
    }
    
    // Continue without authentication
    next();
  }
};

// Alias for authenticate function to match route imports
export const authenticateToken = authenticate;

