import {
  ForbiddenException,
  Injectable,
  NotFoundException,
  UnauthorizedException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { InjectRepository } from '@nestjs/typeorm';
import { jwtConstants } from 'src/constants/auth';
import { DataSource, Repository } from 'typeorm';
import { tokenInterface } from '../interfaces/tokenInteface';
import { comparePassword, encryptPassword } from '../../../utils/encryption';
import { UserEntity } from 'src/entities/User.entity';
import { LoginUserDto } from '../dto/request/logins.dto';

/**
 * AuthService - Core authentication service handling user authentication and token generation
 *
 * This service provides authentication functionality including:
 * - User credential validation
 * - JWT token generation and management
 * - User status checks (active/inactive)
 *
 * Follows security best practices for password comparison and token management.
 *
 * @example
 * // Authentication flow
 * const user = await authService.authCheck(loginDto);
 * const tokens = await authService.generateToken(user);
 */
@Injectable()
export class AuthService {
  constructor(
    private readonly dataSource: DataSource,
    @InjectRepository(UserEntity)
    private readonly userRepository: Repository<UserEntity>,
    private readonly jwtService: JwtService,
    private readonly config: ConfigService,
  ) {}

  /**
   * Validates user credentials and account status
   *
   * Performs comprehensive authentication check:
   * 1. Verifies user exists with provided email
   * 2. Checks if account is active
   * 3. Validates password against stored hash
   *
   * @param loginDto - Login credentials (email and password)
   * @returns The authenticated UserEntity if validation passes
   * @throws NotFoundException if user doesn't exist
   * @throws UnauthorizedException if account is inactive
   * @throws ForbiddenException if password is invalid
   *
   * @note Uses constant-time password comparison to prevent timing attacks
   * @note Returns generic error messages to prevent user enumeration
   *
   * @example
   * const user = await authService.authCheck({
   *   email: 'user@example.com',
   *   password: 'password123'
   * });
   */
  async authCheck(loginDto: LoginUserDto): Promise<UserEntity> {
    // Step 1: Find user by email (case-sensitive lookup)
    const user = await this.userRepository.findOneBy({
      email: loginDto.email,
    });

    if (!user) {
      // Generic error message to prevent email enumeration
      throw new NotFoundException(
        `Account unavailable with this email address.`,
      );
    }

    // Step 2: Check if account is active
    if (!user?.active) {
      throw new UnauthorizedException(`Your account is inactive.`);
    }

    // Step 3: Validate password against stored hash
    const isMatch = await comparePassword(loginDto.password, user?.password);
    if (!isMatch) {
      throw new ForbiddenException('Invalid password');
    }

    return user;
  }

  /**
   * Generates both access and refresh tokens for authenticated user
   *
   * Creates a token pair for maintaining user sessions:
   * - Access token: Short-lived token for API authorization
   * - Refresh token: Long-lived token for obtaining new access tokens
   *
   * @param user - Authenticated user entity
   * @returns Token interface containing both tokens and expiration
   *
   * @note Tokens are generated in parallel for performance
   * @note Refresh token expiration is configured separately
   *
   * @example
   * const tokens = await authService.generateToken(user);
   * // Returns: { access_token: '...', refresh_token: '...', expires_in: 3600 }
   */
  async generateToken(user: UserEntity): Promise<tokenInterface> {
    // Generate tokens in parallel for better performance
    const [access_token, refresh_token] = await Promise.all([
      this.getToken(user),
      this.getToken(user, true),
    ]);

    return {
      access_token,
      refresh_token,
      expires_in: jwtConstants.refreshtokenexpirarytime,
    };
  }

  /**
   * Private helper to generate JWT tokens
   *
   * Creates signed JWT tokens with user claims and configurable expiration.
   * Can generate either access tokens or refresh tokens based on flag.
   *
   * @param user - User entity for token claims
   * @param is_refresh - Flag indicating if generating refresh token (default: false)
   * @returns Signed JWT token string
   *
   * @note Token claims include minimal required data (sub, email, role)
   * @note Uses different expiration times for access vs refresh tokens
   * @note JWT secret is loaded from environment configuration
   */
  private async getToken(
    user: UserEntity,
    is_refresh = false,
  ): Promise<string> {
    // Prepare token payload with user claims
    const payload = {
      sub: user.id ?? (user as any).sub, // Support both UserEntity and possibly other user objects
      email: user.email,
      role: '2', // Hardcoded role - consider making this dynamic based on user.role
    };

    // Configure token expiration based on type
    const expiresIn = is_refresh
      ? jwtConstants.refreshtokenexpirary
      : jwtConstants.accesstokenexpirary;

    // Sign and return the JWT token
    return this.jwtService.signAsync(payload, {
      secret: this.config.get('JWT_SECRET'),
      expiresIn: expiresIn,
    });
  }
}
