import {
  BadRequestException,
  ForbiddenException,
  Injectable,
  NotFoundException,
  UnauthorizedException,
} from "@nestjs/common";
import { UsersService } from "../users/users.service";
import { AppConfigService } from "../config/config.service";
import { JwtService } from "@nestjs/jwt";
import { RefreshTokenDto } from "./dto/refresh-token.dto";
import { RegisterDto } from "./dto/register.dto";
import { LoginDto } from "./dto/login.dto";
import { AccountType } from "@prisma/client";
import { SocialUser, UserToken, User } from '../types/common';
import { PrismaService } from "src/prisma/prisma.service";
import * as argon2 from "argon2";
import { ChangePasswordDto } from "./dto/changePassword.dto";

@Injectable()
export class AuthService {

  constructor(
    private usersService: UsersService,
    private jwtService: JwtService,
    private configService: AppConfigService,
    private prisma: PrismaService,
  ) {
  }

  async validateUser(email: string, password: string): Promise<User> {
    const user = await this.usersService.findByEmail(email);

    if (!user) {
      throw new UnauthorizedException("Invalid credentials");
    }

    if (!user.isActive) {
      throw new ForbiddenException("Your account has been deactivated. Please contact support for assistance.");
    }

    if (!user.password) {
      throw new UnauthorizedException(
        "Password login not available for this account"
      );
    }

    const passwordIsValid = await this.usersService.verifyPassword(
      user.password,
      password
    );

    if (!passwordIsValid) {
      throw new UnauthorizedException("Invalid credentials");
    }

    return user as User;
  }

  async login(user: User, otp?: string) {
    const tokens = await this.getTokens(user.id, user.email, user.role as AccountType);
    await this.usersService.updateRefreshToken(user.id, tokens.refreshToken);

    return {
      message: "Login successful",
      verified: user.isEmailVerified,
      otp: otp,
      user: user as User,
      ...tokens,
    };
  }

  async loginWithCredentials(loginDto: LoginDto) {
    loginDto.email = loginDto.email.toLowerCase();

    const user = await this.validateUser(loginDto.email, loginDto.password);


    // Agar verified hai, token generate karo
    const tokens = await this.getTokens(user.id, user.email, user.role as AccountType);
    await this.usersService.updateRefreshToken(user.id, tokens.refreshToken);

    return {
      message: "Login successful",
      verified: true,
      user: user,
      token: {
        ...tokens,
      }
    };
  }



  async register(registerDto: RegisterDto) {
    const user = await this.usersService.create(registerDto);
    await this.usersService.updateEmailVerified(user.id, true);
    user.isEmailVerified = true;

    const tokens = await this.getTokens(user.id, user.email, user.role as AccountType);
    await this.usersService.updateRefreshToken(user.id, tokens.refreshToken);

    return {
      message: "Registration successful.",
      user: user,
      token: {
        ...tokens,
      }
    };
  }

  async refreshTokens(refreshTokenDto: RefreshTokenDto) {
    try {
      const { refreshToken } = refreshTokenDto;
      const payload = this.jwtService.verify<UserToken>(refreshToken, {
        secret: this.configService.get<string>("JWT_REFRESH_SECRET") as any,
      }) as User;

      const user = await this.usersService.findByEmail(payload.email);

      if (!user || !user.refreshToken) {
        throw new ForbiddenException("Access denied");
      }

      const refreshTokenMatches = await this.usersService.verifyPassword(
        user.refreshToken,
        refreshToken
      );

      if (!refreshTokenMatches) {
        throw new ForbiddenException("Access denied");
      }

      const tokens = await this.getTokens(
        user.id,
        user.email,
        user.role as AccountType
      );
      await this.usersService.updateRefreshToken(user.id, tokens.refreshToken);

      return tokens;
    } catch (error) {
      throw new ForbiddenException("Invalid refresh token");
    }
  }

  private async getTokens(
    userId: string,
    email: string,
    role: AccountType
  ) {
    const [accessToken, refreshToken] = await Promise.all([
      this.jwtService.signAsync(
        {
          sub: userId,
          email,
          role,
        },
        {
          secret: this.configService.get<string>("JWT_SECRET") as any,
          expiresIn: this.configService.get<string>("JWT_EXPIRES_IN") as any,
        }
      ),
      this.jwtService.signAsync(
        {
          sub: userId,
          email,
          role,
        },
        {
          secret: this.configService.get<string>("JWT_REFRESH_SECRET") as any,
          expiresIn: this.configService.get<string>("JWT_REFRESH_EXPIRES_IN") as any,
        }
      ),
    ]);

    return {
      accessToken,
      refreshToken,
      expiresIn: this.configService.get<string>("JWT_EXPIRES_IN") as any,
      tokenType: "Bearer",
    };
  }









  async changePassword(changePasswordDto: ChangePasswordDto, id: string) {
    const user = await this.prisma.users.findFirst({
      where: { id },
    });

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

    if (!user.password) {
      throw new BadRequestException(
        "Password login not available for this account"
      );
    }

    const valid = await this.usersService.verifyPassword(
      user.password,
      changePasswordDto.currentPassword
    );

    if (!valid) {
      throw new BadRequestException("Current password is incorrect");
    }

    const isSamePassword = await argon2.verify(
      user.password,
      changePasswordDto.newPassword
    );

    if (isSamePassword) {
      throw new BadRequestException(
        "New password must be different from the current password"
      );
    }

    const hashedPassword = await argon2.hash(changePasswordDto.newPassword);

    await this.prisma.users.update({
      where: { id },
      data: {
        password: hashedPassword,
      },
    });

    return { message: "Password Change Successfully." };
  }

  async socialLogin(socialUser: SocialUser) {
    let user = await this.usersService.findByEmail(socialUser.email);
    if (!user) {
      user = await this.usersService.createSocialUser(socialUser);
    }

    const tokens = await this.getTokens(user.id, user.email, user.role as AccountType);
    await this.usersService.updateRefreshToken(user.id, tokens.refreshToken);

    return {
      message: "Registration & Login successful",
      verified: user.isEmailVerified,
      user: user as User,
      ...tokens,
    };
  }

  async forgotPassword(email: string) {
    const user = await this.usersService.findByEmail(email.toLowerCase());
    if (!user) {
      throw new NotFoundException("User not found");
    }

    const resetToken = this.jwtService.sign(
      { sub: user.id, email: user.email },
      { secret: this.configService.get<string>("JWT_SECRET"), expiresIn: '15m' }
    );

    const hashedResetToken = await argon2.hash(resetToken);
    const resetTokenExpires = new Date(Date.now() + 15 * 60 * 1000); // 15 minutes from now

    await this.prisma.users.update({
      where: { id: user.id },
      data: {
        passwordResetToken: hashedResetToken,
        passwordResetExpires: resetTokenExpires,
      },
    });

    const resetLink = `${this.configService.get<string>("FRONTEND_URL")}/reset-password?token=${resetToken}`;
    console.log(resetLink);    

    return { message: "Password reset email sent" };
  }

  async resetPassword(token: string, newPassword: string) {
    try {
      const payload = this.jwtService.verify(token, {
        secret: this.configService.get<string>("JWT_SECRET"),
      });

      const user = await this.prisma.users.findUnique({
        where: { email: payload.email.toLowerCase() },
      });

      if (
        !user ||
        !user.passwordResetToken ||
        !user.passwordResetExpires ||
        user.passwordResetExpires < new Date() ||
        !(await argon2.verify(user.passwordResetToken, token))
      ) {
        throw new BadRequestException("Invalid or expired reset token");
      }

      const hashedPassword = await argon2.hash(newPassword);

      await this.prisma.users.update({
        where: { id: user.id },
        data: {
          password: hashedPassword,
          passwordResetToken: null,
          passwordResetExpires: null,
        },
      });

      return { message: "Password reset successfully" };
    } catch (error) {
      throw new BadRequestException("Invalid or expired reset token");
    }
  }
}