import {
  Injectable,
  HttpException,
  HttpStatus,
  Logger,
  UseInterceptors,
  ClassSerializerInterceptor,
  InternalServerErrorException,
  BadRequestException,
  NotFoundException,
  UnauthorizedException,
  ConflictException,
} from '@nestjs/common';
import { compare, hash } from 'bcrypt';
import { ConfigService } from '@nestjs/config';
import { DataStoredInToken, TokenData } from 'modules/auth/interfaces/auth.interface';
import { JwtService } from '@nestjs/jwt';
import { UserService } from '../user/users.service';
import { UserResponseDto } from 'modules/user/dto/user-response.dto';
// import { IUser } from 'modules/user/interface/users.interface';
import { UserRole } from 'common/enum/UserRole.enum';
import { IGoogleUser } from './interfaces/google-user.interface';
import { CreateUserDto } from 'modules/user/dto/create-user.dto';
import { UpdateUserDto } from 'modules/user/dto/update-user.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { UserEntity } from 'modules/user/entity/users.entity';
import { Repository } from 'typeorm';
import { mapUserToDto } from 'modules/user/dto/map-user.dto';
import { UserLoginDto } from './dtos/user-login.dto';
import * as nodemailer from 'nodemailer';
import { VerifyEmailDto } from './dtos/verify-email.dto';

@Injectable()
@UseInterceptors(ClassSerializerInterceptor)
export class AuthService {
  private readonly logger = new Logger(AuthService.name);
  private transporter: nodemailer.Transporter;
  private readonly emailVerificationTokenExpiry = 3600000; // 1 hour

  constructor(
    private readonly userService: UserService,
    private readonly configService: ConfigService,
    private readonly jwtService: JwtService,
    @InjectRepository(UserEntity)
    private readonly userRepository: Repository<UserEntity>,
  ) {
    this.initializeEmailTransporter();
  }
  private initializeEmailTransporter(): void {
    try {
      const smtpConfig = {
        host: this.configService.get<string>('AUTH_SMTP_HOST'),
        port: this.configService.get<number>('AUTH_SMTP_PORT'),
        secure: true,
        auth: {
          user: this.configService.get<string>('AUTH_SMTP_USER'),
          pass: this.configService.get<string>('AUTH_SMTP_PASSWORD'),
        },
        tls: {
          rejectUnauthorized: false,
        },
        // Add connection timeout and other robust settings
        connectionTimeout: 60000, // 60 seconds
        greetingTimeout: 30000, // 30 seconds
        socketTimeout: 60000, // 60 seconds
        // Add pool settings for better connection management
        pool: true,
        maxConnections: 5,
        maxMessages: 100,
        // Add retry settings
        retryDelay: 1000, // 1 second
        maxRetries: 3,
      };

      this.transporter = nodemailer.createTransport(smtpConfig);

      // Verify the connection configuration
      this.verifyEmailConnection();

      this.logger.debug('Email transporter initialized successfully');
    } catch (error) {
      this.logger.error('Failed to initialize email transporter:', error);
      // Don't throw error here - let the service start but log the issue
      this.logger.warn('Email service will be disabled due to configuration error');
    }
  }

  private async verifyEmailConnection(): Promise<void> {
    try {
      await this.transporter.verify();
      this.logger.debug('Email transporter connection verified successfully');
    } catch (error) {
      this.logger.error('Email transporter verification failed:', error);
      this.logger.warn('Email functionality may not work properly');
    }
  }

  private generateEmailVerificationToken(email: string): string {
    this.logger.debug(`Generating email verification token for ${email}`);
    const payload = {
      email,
      type: 'email_verification',
      timestamp: Date.now(),
    };
    const token = this.jwtService.sign(payload, {
      secret: this.configService.get<string>('EMAIL_VERIFICATION_SECRET'),
      expiresIn: '1h',
    });
    return token;
  }

  private createToken(user: UserResponseDto): TokenData {
    const dataStoredInToken: DataStoredInToken = {
      id: user.id,
      role: user.role,
      email: user.email,
      isEmailVerified: user.isEmailVerified,
    };
    const expiresIn = this.configService.get<number>('JWT_EXPIRES_IN') || 36000; // 1 day default
    const token = this.jwtService.sign(dataStoredInToken, {
      secret: this.configService.get<string>('JWT_SECRET'),
      expiresIn,
    });
    return { expiresIn, token };
  }

  private getCookieOptions(tokenData: TokenData): { name: string; value: string; options: any } {
    this.logger.debug('Generating cookie options for token', { token: tokenData.token, expiresIn: tokenData.expiresIn });
    return {
      name: 'Authentication',
      value: tokenData.token,
      options: {
        httpOnly: true,
        secure: this.configService.get<string>('NODE_ENV') === 'production',
        sameSite: 'strict',
        maxAge: 7 * 24 * 60 * 60 * 1000, // Convert seconds to milliseconds
        path: '/',
      },
    };
  }

  async validateUser(email: string, password: string, roles: UserRole[]): Promise<UserResponseDto> {
    const existingUser = await this.userRepository.findOne({
      where: { email },
    });

    if (!existingUser) {
      throw new HttpException(`This email ${email} was not found`, HttpStatus.NOT_FOUND);
    }

    if (!roles.includes(existingUser.role)) {
      this.logger.error('Unauthorized role mismatch', {
        existingRole: existingUser.role,
        allowedRoles: roles,
      });
      throw new HttpException('Unauthorized', HttpStatus.UNAUTHORIZED);
    }

    const isPasswordMatching = await compare(password, existingUser.password);
    if (!isPasswordMatching) {
      throw new HttpException('Password not matching', HttpStatus.UNAUTHORIZED);
    }

    return mapUserToDto(existingUser);
  }

  private async sendEmail(to: string, subject: string, html: string): Promise<void> {
    const mailOptions = {
      from: this.configService.get<string>('AUTH_SMTP_USER'),
      to,
      subject,
      html,
    };

    try {
      await this.transporter.sendMail(mailOptions);
      this.logger.debug(`Email sent successfully to ${to}`);
    } catch (error) {
      this.logger.error(`Failed to send email to ${to}:`, error);
      throw new InternalServerErrorException('Failed to send email');
    }
  }

  // Async email sending helper to avoid blocking user creation
  private async sendVerificationEmailAsync(email: string, token: string, firstName?: string): Promise<void> {
    this.logger.debug(`Sending verification email to ${email}`);
    try {
      const verificationUrl = `${this.configService.get<string>('FRONTEND_URL')}/verify-email?token=${token}&email=${encodeURIComponent(email)}`;

      const emailHtml = this.generateVerificationEmailTemplate(verificationUrl, firstName);

      await this.sendEmail(email, 'Email Verification', emailHtml);

      this.logger.debug(`Verification email sent to ${email}`);
    } catch (error) {
      this.logger.error(`Failed to send verification email to ${email}:`, error);
      throw error;
    }
  }

  // Extract email template to separate method for better maintainability
  private generateVerificationEmailTemplate(verificationUrl: string, firstName?: string): string {
    return `
    <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
      <h2 style="color: #333; margin-bottom: 20px;">Email Verification</h2>
      <p>Hello ${firstName || 'User'},</p>
      <p>Thank you for signing up! Please click the link below to verify your email address:</p>
      <div style="text-align: center; margin: 30px 0;">
        <a href="${verificationUrl}" 
           style="background-color: #007bff; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; display: inline-block; font-weight: bold;">
          Verify Email Address
        </a>
      </div>
      <p style="color: #666; font-size: 14px;">This link will expire in 1 hour.</p>
      <p style="color: #666; font-size: 14px;">If you did not request this verification, please ignore this email.</p>
      <hr style="border: none; border-top: 1px solid #eee; margin: 30px 0;">
      <p style="color: #666; font-size: 12px;">Best regards,<br>The Team</p>
    </div>
  `;
  }

  // Optimized email verification sending (removes redundant checks)
  async sendEmailVerification(email: string): Promise<{ message: string }> {
    const normalizedEmail = email.toLowerCase().trim();
    if (!normalizedEmail) {
      throw new BadRequestException('Email is required');
    }

    const user = await this.userRepository.findOne({
      where: { email: normalizedEmail },
      select: ['id', 'email', 'firstName', 'isEmailVerified', 'emailVerificationTokenExpiresAt'],
    });

    if (!user) {
      throw new NotFoundException('User not found');
    }

    if (user.isEmailVerified) {
      throw new BadRequestException('Email is already verified');
    }

    // Rate limiting check
    if (user.emailVerificationTokenExpiresAt && user.emailVerificationTokenExpiresAt > new Date()) {
      const timeRemaining = Math.ceil((user.emailVerificationTokenExpiresAt.getTime() - Date.now()) / 60000);
      throw new BadRequestException(`Please wait ${timeRemaining} minutes before requesting another verification email`);
    }

    // Generate new token and update user
    const verificationToken = this.generateEmailVerificationToken(normalizedEmail);
    await this.userRepository.update(user.id, {
      emailVerificationToken: verificationToken,
      emailVerificationTokenExpiresAt: new Date(Date.now() + this.emailVerificationTokenExpiry),
    });

    // Send email
    await this.sendVerificationEmailAsync(normalizedEmail, verificationToken, user.firstName);

    return { message: 'Verification email sent successfully' };
  }

  // Optimized email verification with better error handling
  async verifyEmail(verifyEmailDto: VerifyEmailDto): Promise<UserResponseDto> {
    const { token, email } = verifyEmailDto;
    const normalizedEmail = email.toLowerCase();

    try {
      // Verify JWT token
      const decoded = this.jwtService.verify(token, {
        secret: this.configService.get<string>('EMAIL_VERIFICATION_SECRET'),
      });

      if (decoded.email !== normalizedEmail || decoded.type !== 'email_verification') {
        throw new UnauthorizedException('Invalid verification token');
      }

      // Find user and validate
      const user = await this.userRepository.findOne({
        where: { email: normalizedEmail },
        select: ['id', 'email', 'firstName', 'lastName', 'isEmailVerified', 'emailVerificationToken', 'emailVerificationTokenExpiresAt'],
      });

      if (!user) {
        throw new NotFoundException('User not found');
      }

      if (user.isEmailVerified) {
        throw new BadRequestException('Email is already verified');
      }

      if (user.emailVerificationToken !== token || !user.emailVerificationTokenExpiresAt || user.emailVerificationTokenExpiresAt < new Date()) {
        throw new UnauthorizedException('Invalid or expired verification token');
      }

      // Update user verification status
      const updatedUser = await this.userRepository.save({
        ...user,
        isEmailVerified: true,
        emailVerifiedAt: new Date(),
        emailVerificationToken: null,
        emailVerificationTokenExpiresAt: null,
      });

      this.logger.log('Email verified successfully', {
        userId: updatedUser.id,
        email: normalizedEmail,
      });

      return mapUserToDto(updatedUser);
    } catch (error) {
      this.logger.error('Error verifying email', {
        email: normalizedEmail,
        error: error.message,
      });

      if (error instanceof HttpException) {
        throw error;
      }
      throw new InternalServerErrorException('Failed to verify email');
    }
  }

  async resendEmailVerification(email: string): Promise<{ message: string }> {
    return this.sendEmailVerification(email);
  }

  public async createUser(userData: CreateUserDto): Promise<UserResponseDto> {
    const normalizedEmail = userData.email.toLowerCase();

    return await this.userRepository.manager.transaction(async transactionalEntityManager => {
      try {
        // Check if user already exists
        const existingUser = await transactionalEntityManager.findOne(UserEntity, {
          where: { email: normalizedEmail },
          select: ['id', 'email'], // Only select needed fields for performance
        });

        if (existingUser) {
          throw new ConflictException(`User with email ${normalizedEmail} already exists`);
        }

        // Validate role
        if (userData.role !== UserRole.USER) {
          throw new BadRequestException('Invalid role for user creation');
        }

        // Hash password and generate verification token in parallel
        const [hashedPassword, verificationToken] = await Promise.all([
          hash(userData.password, 12), // Increased salt rounds for better security
          this.generateEmailVerificationToken(normalizedEmail),
        ]);

        // Create user with verification token
        const newUser = transactionalEntityManager.create(UserEntity, {
          ...userData,
          email: normalizedEmail,
          password: hashedPassword,
          emailVerificationToken: verificationToken,
          emailVerificationTokenExpiresAt: new Date(Date.now() + this.emailVerificationTokenExpiry),
          isEmailVerified: false,
          planClient: null,
          clientId: null,
          linkedClientId: null,
          linkedClient: null,
        });

        const savedUser = await transactionalEntityManager.save(newUser);

        this.logger.debug('User created successfully', {
          userId: savedUser.id,
          email: normalizedEmail,
        });

        // Send verification email asynchronously (don't wait for it to complete)
        this.sendVerificationEmailAsync(normalizedEmail, verificationToken, savedUser.firstName).catch(error => {
          this.logger.error(`Failed to send verification email to ${normalizedEmail}:`, error);
          // Could implement retry logic or queue here
        });

        this.logger.log('User created successfully', {
          userId: savedUser.id,
          email: normalizedEmail,
        });

        return mapUserToDto(savedUser);
      } catch (error) {
        this.logger.error('Error creating user', {
          email: normalizedEmail,
          error: error.message,
        });

        if (error instanceof HttpException) {
          throw error;
        }
        throw new InternalServerErrorException('Failed to create user');
      }
    });
  }

  async login(userData: UserLoginDto): Promise<{ cookieData: any; token: string; findUser: UserResponseDto }> {
    const allowedRoles = [UserRole.USER, UserRole.CLIENT];
    const validatedUser = await this.validateUser(userData.email, userData.password, allowedRoles);
    const tokenData = this.createToken(validatedUser);
    const cookieData = this.getCookieOptions(tokenData);

    return {
      cookieData,
      token: tokenData.token,
      findUser: validatedUser,
    };
  }

  async adminLogin(userData: UserLoginDto): Promise<{ cookieData: any; token: string; findUser: UserResponseDto }> {
    const allowedRoles = [UserRole.ADMIN, UserRole.SUPER_ADMIN];
    const validatedUser = await this.validateUser(userData.email, userData.password, allowedRoles);
    const tokenData = this.createToken(validatedUser);
    const cookieData = this.getCookieOptions(tokenData);

    this.logger.debug('Admin login successful', { email: validatedUser.email, role: validatedUser.role });
    return {
      cookieData,
      token: tokenData.token,
      findUser: validatedUser,
    };
  }

  async googleLogin(userData: IGoogleUser): Promise<{ cookie: string; user: UserResponseDto }> {
    this.logger.debug('Google login attempt', { userData });
    if (!userData.email) {
      throw new HttpException('Invalid user data', HttpStatus.BAD_REQUEST);
    }

    this.logger.debug('Finding user by email');
    const findUser = await this.userRepository.findOne({
      where: {
        email: userData.email,
      },
    });

    try {
      if (!findUser) {
        this.logger.debug('User not found, creating new user');
        const newUser: CreateUserDto = {
          email: userData.email,
          isEmailVerified: true,
          password: userData.googleId,
          role: UserRole.USER,
          referralMail: null,
          firstName: userData.firstName,
          lastName: userData.lastName,
          picture: userData.picture,
          googleId: userData.googleId,
          isGoogleUser: true,
        };

        const createdUser = await this.createUser(newUser);
        const tokenData = this.createToken(createdUser);
        const cookie = tokenData.token;
        return { cookie, user: createdUser };
      } else {
        // TODO: User already exists, update user with googleId
        // if (!findUser.isGoogleUser) {
        //   throw new HttpException('User already exists with this email', HttpStatus.BAD_REQUEST);
        // }
        const user: UpdateUserDto = {
          // email: userData.email,
          referralMail: null,
          // firstName: userData.firstName,
          // lastName: userData.lastName,
          picture: userData.picture,
          googleId: userData.googleId,
          isGoogleUser: true,
        };
        const updatedUser = await this.userService.updateUser(findUser.id, user);
        const tokenData = this.createToken(updatedUser);
        const cookie = tokenData.token;
        return { cookie, user: updatedUser };
      }
    } catch (error: any) {
      throw new HttpException(error.message, HttpStatus.UNAUTHORIZED);
    }
  }

  async googleLoginUrl(state?: string): Promise<{ url: string }> {
    const googleClientId = this.configService.get<string>('GOOGLE_CLIENT_ID');
    const callbackUrl = this.configService.get<string>('GOOGLE_CALLBACK_URL');

    let url = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${googleClientId}&response_type=code&scope=openid%20profile%20email&redirect_uri=${encodeURIComponent(callbackUrl)}`;

    // Add state parameter if provided
    if (state) {
      url += `&state=${encodeURIComponent(state)}`;
    }

    return { url };
  }

  async logout(): Promise<{ message: string; cookie: string }> {
    this.logger.debug('User logout');
    const cookie = 'Authentication=; HttpOnly; Path=/; Max-Age=0;';
    const refreshTokenCookie = 'refreshToken=; HttpOnly; Path=/; Max-Age=0;';
    const sessionCookie = 'session=; HttpOnly; Path=/; Max-Age=0;';
    const cookiesToClear = `${cookie} ${refreshTokenCookie} ${sessionCookie}`;

    return {
      message: 'Logout successful',
      cookie: cookiesToClear,
    };
  }

  async ChangeUserPassword(userId: number, oldPassword: string, newPassword: string): Promise<UserResponseDto> {
    try {
      this.logger.debug(`Changing password for user ID: ${userId}`);
      const user = await this.userRepository.findOne({ where: { id: userId } });

      if (!user) {
        throw new HttpException('User not found', HttpStatus.NOT_FOUND);
      }

      const isPasswordMatching = await compare(oldPassword, user.password);
      if (!isPasswordMatching) {
        throw new HttpException('Old password is incorrect', HttpStatus.UNAUTHORIZED);
      }

      user.password = newPassword;
      const updatedUser = await this.userRepository.save(user);
      this.logger.debug(`Password changed successfully for user ID: ${userId}`);
      return mapUserToDto(updatedUser);
    } catch (error) {
      this.logger.error(`Error changing password for user ID ${userId}:`, error);
      if (error instanceof HttpException) {
        throw error; // Re-throw known exceptions
      }
      throw new HttpException('Failed to change password', HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  async resetPasswordByEmail(email: string, newPassword: string): Promise<UserResponseDto> {
    try {
      this.logger.debug(`Resetting password for email: ${email}`);
      const user = await this.userRepository.findOne({ where: { email } });

      if (!user) {
        throw new HttpException('User not found', HttpStatus.NOT_FOUND);
      }

      const isPasswordMatching = await compare(newPassword, user.password);
      if (isPasswordMatching) {
        throw new HttpException('New password cannot be the same as the old password', HttpStatus.BAD_REQUEST);
      }

      // Hash the new password before saving
      const hashPassword = await hash(newPassword, 10);
      if (hashPassword) {
        throw new HttpException('Failed to hash new password', HttpStatus.INTERNAL_SERVER_ERROR);
      }

      user.password = hashPassword;

      const updatedUser = await this.userRepository.save(user);
      this.logger.debug(`Password reset successfully for email: ${email}`);
      return mapUserToDto(updatedUser);
    } catch (error) {
      this.logger.error(`Error resetting password for user email ${email}:`, error);
      if (error instanceof HttpException) {
        throw error; // Re-throw known exceptions
      }
      throw new HttpException('Failed to reset password', HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }
}
