import {
  BadRequestException,
  Injectable,
  NotFoundException,
  UnauthorizedException,
  ForbiddenException,
} from '@nestjs/common';
import { UsersService } from '../users/users.service';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';
import { randomBytes } from 'crypto';
import { MailService } from '../mail/mail.service';

@Injectable()
export class AuthService {
  constructor(
    private usersService: UsersService,
    private jwtService: JwtService,
    private mailService: MailService,
  ) { }

  async register(data: {
    email: string;
    password: string;
    phone?: string;
    gender?: string;
    role?: string;
  }) {
    const existingUser = await this.usersService.findByEmail(data.email);
    if (existingUser) {
      throw new BadRequestException('Email already exists');
    }

    const hashed = await bcrypt.hash(data.password, 10);

    // Generate 6-digit OTP
    const verificationOtp = Math.floor(100000 + Math.random() * 900000).toString();
    const otpExpires = new Date();
    otpExpires.setMinutes(otpExpires.getMinutes() + 10); // Expire in 10 minutes

    const user = await this.usersService.create({
      email: data.email,
      password: hashed,
      phone: data.phone,
      gender: data.gender,
      role: data.role || 'learner',
    });

    user.verificationOtp = verificationOtp;
    user.otpExpires = otpExpires;
    await this.usersService.save(user);

    await this.mailService.sendOtpEmail(user.email, verificationOtp);

    return { message: 'OTP sent to your email. Please verify your account.', email: user.email };
  }

  async verifyOtp(email: string, otp: string) {
    const user = await this.usersService.findByEmail(email);
    if (!user) {
      throw new BadRequestException('User not found');
    }

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

    if (user.verificationOtp !== otp) {
      throw new BadRequestException('Invalid OTP');
    }

    if (!user.otpExpires || new Date() > user.otpExpires) {
      throw new BadRequestException('OTP has expired. Please request a new one.');
    }

    user.isVerified = true;
    user.verificationOtp = null;
    user.otpExpires = null;
    await this.usersService.save(user);

    // Auto-login: generate tokens
    const accessToken = this.jwtService.sign({
      sub: user.id,
      email: user.email,
      name: user.email.split('@')[0],
      role: user.role,
    }, { expiresIn: '30d' });

    const refreshToken = this.jwtService.sign(
      { sub: user.id },
      { expiresIn: '365d' },
    );

    return {
      message: 'Email verified successfully. Logging you in...',
      access_token: accessToken,
      refresh_token: refreshToken,
      role: user.role
    };
  }

  async resendOtp(email: string) {
    const user = await this.usersService.findByEmail(email);
    if (!user) throw new BadRequestException('User not found');
    if (user.isVerified) throw new BadRequestException('Email already verified');

    const newOtp = Math.floor(100000 + Math.random() * 900000).toString();
    const otpExpires = new Date();
    otpExpires.setMinutes(otpExpires.getMinutes() + 10);

    user.verificationOtp = newOtp;
    user.otpExpires = otpExpires;
    await this.usersService.save(user);

    await this.mailService.sendOtpEmail(user.email, newOtp);

    return { message: 'New OTP sent to your email.' };
  }

  async login(email: string, password: string) {
    const user = await this.usersService.findByEmail(email);
    if (!user) throw new NotFoundException('User not found');

    const valid = await bcrypt.compare(password, user.password);
    if (!valid) {
      throw new UnauthorizedException('Invalid credentials');
    }

    // Check if verified
    if (!user.isVerified) {
      throw new ForbiddenException('Please verify your email address before logging in.');
    }

    const accessToken = this.jwtService.sign({
      sub: user.id,
      email: user.email,
      name: user.email.split('@')[0],
      role: user.role,
    });
    const refreshToken = this.jwtService.sign(
      { sub: user.id },
      { expiresIn: '365d' },
    );

    return { access_token: accessToken, refresh_token: refreshToken };
  }

  async forgotPassword(email: string) {
    const user = await this.usersService.findByEmail(email);
    if (!user) return { error: 'User not found' };

    const token = randomBytes(32).toString('hex');
    user.resetToken = token;
    await this.usersService.save(user);

    // Send reset password email
    await this.mailService.sendPasswordResetEmail(user.email, token);

    return { message: 'Reset link sent to your email.' };
  }

  async resetPassword(token: string, newPassword: string) {
    const user = await this.usersService.findByResetToken(token);
    if (!user) throw new BadRequestException('Invalid or expired reset token');

    user.password = await bcrypt.hash(newPassword, 10);
    user.resetToken = null;
    await this.usersService.save(user);

    return { message: 'Password reset successful' };
  }

  async socialLogin(data: { email: string; name?: string; image?: string; provider?: string; role?: string }) {
    let user = await this.usersService.findByEmail(data.email);

    if (!user) {
      // If no role provided (from Login page), mark as pending
      // If role provided (from Register page cookie), use it directly
      const finalRole = data.role || 'social_pending';

      user = await this.usersService.create({
        email: data.email,
        password: '',
        role: finalRole,
        isVerified: true,
      });
    }

    const accessToken = this.jwtService.sign({
      sub: user.id,
      email: user.email,
      name: user.email.split('@')[0], // Default name from email
      role: user.role,
    }, { expiresIn: '30d' }); // Much longer for social logins

    const refreshToken = this.jwtService.sign(
      { sub: user.id },
      { expiresIn: '365d' },
    );

    return { access_token: accessToken, refresh_token: refreshToken };
  }

  async updateSocialRole(userId: number, role: string) {
    const user = await this.usersService.findById(userId);
    if (!user) throw new NotFoundException('User not found');
    if (user.role !== 'social_pending') {
      throw new BadRequestException('Role already set or not eligible for social update');
    }

    user.role = role;
    await this.usersService.save(user);

    const accessToken = this.jwtService.sign({
      sub: user.id,
      email: user.email,
      name: user.email.split('@')[0],
      role: user.role,
    }, { expiresIn: '30d' });

    return { access_token: accessToken, role: user.role };
  }
}
