import { generateResetPasswordToken } from "../../utils/generateResetPasswordToken";
import { generateOtpEmailTemplate } from "../../utils/generateOtpEmailTemplate";
import { resetEmailTemplate } from "../../utils/generateEmailTemplate";
import { catchAsyncError } from "../../middlewares/catchAsyncError";
import { generateToken, verifyToken } from "../../utils/jwtToken";
import { Request, Response, NextFunction } from "express";
import { generateOtp } from "../../utils/generateOtp";
import ErrorHandler from "../../middlewares/error";
import { sendEmail } from "../../utils/sendEmail";
import database from "../../config/db";
import crypto from "crypto";
import bcrypt from "bcrypt";
import axios from "axios";   


// controller: register
export const register = catchAsyncError(async (req: Request, res: Response, next: NextFunction) => {
  const {
    account_type = 'user',
    username,
    email,
    password,
    display_name,
    phone_number,
    location_city,
    location_country
  } = req.body;

  // Validation
  if (!username || !email || !password) {
    return next(new ErrorHandler("Username, email and password are required.", 400));
  }

  if (password.length < 8 || password.length > 16) {
    return next(
      new ErrorHandler("Password must be between 8 & 16 characters.", 400)
    );
  }

  // Username validation regex
  const usernameRegex = /^[a-zA-Z0-9_]{3,50}$/;
  if (!usernameRegex.test(username)) {
    return next(
      new ErrorHandler(
        "Username must be 3-50 characters long and can only contain letters, numbers, and underscores.",
        400
      )
    );
  }

  // Email validation regex
  const emailRegex = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/;
  if (!emailRegex.test(email)) {
    return next(new ErrorHandler("Invalid email format.", 400));
  }

  // Check if username or email already exists
  const existingAccount = await database.query(
    `SELECT * FROM accounts WHERE username = $1 OR email = $2`,
    [username, email]
  );

  if (existingAccount.rows.length > 0) {
    const existing = existingAccount.rows[0];
    if (existing.username === username) {
      return next(new ErrorHandler("Username already taken.", 400));
    }
    if (existing.email === email) {
      return next(new ErrorHandler("Email already registered.", 400));
    }
  }

  // Validate account type specific fields
  if (account_type === 'user') {
    const { user_full_name } = req.body;
    if (!user_full_name) {
      return next(new ErrorHandler("Full name is required for user accounts.", 400));
    }
  } else if (account_type === 'company') {
    const { company_name } = req.body;
    if (!company_name) {
      return next(new ErrorHandler("Company name is required for company accounts.", 400));
    }
  } else {
    return next(new ErrorHandler("Invalid account type.", 400));
  }

  // Hash password
  const hashedPassword = await bcrypt.hash(password, 12);

  // Generate OTP
  const { otp, otpExpireTime } = generateOtp();

  // Insert into database
  const result = await database.query(
    `INSERT INTO accounts (
      account_type,
      username,
      email,
      password_hash,
      display_name,
      phone_number,
      location_city,
      location_country,
      otp_code,
      otp_expires_at,
      account_status,
      user_full_name,
      company_name,
      user_current_company,
      company_description,
      user_job_title
    ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, to_timestamp($10), $11, $12, $13, $14, $15, $16) 
    RETURNING *`,
    [
      account_type,
      username,
      email,
      hashedPassword,
      display_name || username,
      phone_number || null,
      location_city || null,
      location_country || null,
      otp,
      otpExpireTime / 1000,
      'pending_verification',
      account_type === 'user' ? req.body.user_full_name : null,
      account_type === 'company' ? req.body.company_name : null,
      req.body.user_current_company || null,
      req.body.company_description || null,
      req.body.user_job_title || null
    ]
  );

  const newAccount = result.rows[0];

  // Send OTP email
  try {
    await sendEmail({
      email: newAccount.email,
      subject: `Verify Your ${account_type === 'company' ? 'Company ' : ''}Toolffy Account`,
      message: generateOtpEmailTemplate(otp, account_type),
    });
  } catch (emailError) {
    // If email fails, delete the account
    await database.query(`DELETE FROM accounts WHERE id = $1`, [newAccount.id]);
    return next(
      new ErrorHandler("Failed to send verification email. Please try again.", 500)
    );
  }

  res.status(201).json({
    success: true,
    message: `Registration successful. OTP sent to ${newAccount.email}.`,
    data: {
      id: newAccount.id,
      username: newAccount.username,
      email: newAccount.email,
      account_type: newAccount.account_type,
      account_status: newAccount.account_status,
    }
  });
});

// controller: verify OTP
export const verifyOtp = catchAsyncError(async (req: Request, res: Response, next: NextFunction) => {
  const { email, otp } = req.body;

  if (!email || !otp) {
    return next(new ErrorHandler("Email and OTP are required.", 400));
  }

  const accountResult = await database.query(
    `SELECT * FROM accounts WHERE email = $1 AND account_status = 'pending_verification'`,
    [email]
  );

  if (accountResult.rows.length === 0) {
    return next(new ErrorHandler("Account not found or already verified.", 404));
  }

  const account = accountResult.rows[0];

  // Check OTP validity
  if (account.otp_code !== otp) {
    // Increment failed attempts or handle OTP mismatch
    return next(new ErrorHandler("Invalid OTP.", 400));
  }

  // if (!account.otp_expires_at || new Date(account.otp_expires_at) < new Date()) {
  //   return next(new ErrorHandler("OTP has expired.", 400));
  // }

  // Verify the account
  await database.query(
    `UPDATE accounts SET 
      is_email_verified = true,
      email_verified_at = CURRENT_TIMESTAMP,
      otp_code = NULL,
      otp_expires_at = NULL,
      account_status = 'active',
      updated_at = CURRENT_TIMESTAMP
    WHERE id = $1`,
    [account.id]
  );

  // Get updated account
  const updatedAccountResult = await database.query(
    `SELECT * FROM accounts WHERE id = $1`,
    [account.id]
  );

  const updatedAccount = updatedAccountResult.rows[0];

  // Generate token
  generateToken(updatedAccount, 200, "Account verified successfully.", res);
});

// controller: resend OTP
export const resendOtp = catchAsyncError(async (req: Request, res: Response, next: NextFunction) => {
  const { email } = req.body;

  if (!email) {
    return next(new ErrorHandler("Email is required.", 400));
  }

  const accountResult = await database.query(
    `SELECT * FROM accounts WHERE email = $1 AND account_status = 'pending_verification'`,
    [email]
  );

  if (accountResult.rows.length === 0) {
    return next(new ErrorHandler("Account not found or already verified.", 404));
  }

  const account = accountResult.rows[0];

  // Generate new OTP
  const { otp, otpExpireTime } = generateOtp();

  // Update OTP in database
  await database.query(
    `UPDATE accounts SET 
      otp_code = $1,
      otp_expires_at = to_timestamp($2),
      updated_at = CURRENT_TIMESTAMP
    WHERE id = $3`,
    [otp, otpExpireTime / 1000, account.id]
  );

  // Send new OTP email
  try {
    await sendEmail({
      email: account.email,
      subject: `New OTP for Your ${account.account_type === 'company' ? 'Company ' : ''}Toolffy Account`,
      message: generateOtpEmailTemplate(otp, account.account_type),
    });
  } catch (emailError) {
    return next(
      new ErrorHandler("Failed to send OTP email. Please try again.", 500)
    );
  }

  res.status(200).json({
    success: true,
    message: `New OTP sent to ${account.email}.`,
  });
});

// controller: login with email or username
export const login = catchAsyncError(async (req: Request, res: Response, next: NextFunction) => {
  const { identifier, password, recaptchaToken, rememberMe = false } = req.body;

  if (!identifier || !password) {
    return next(new ErrorHandler("Identifier and password are required.", 400));
  }

    // Verify reCAPTCHA token
  // const { data } = await axios.post(
  //   `https://www.google.com/recaptcha/api/siteverify`,
  //   null,
  //   {
  //     params: {
  //       secret: process.env.RECAPTCHA_SECRET_KEY,
  //       response: recaptchaToken,
  //     },
  //   }
  // ); 

  // if (!data.success || data.score < 0.5) {
  //   return next(new ErrorHandler("Failed reCAPTCHA verification", 403));
  // }

  // Determine if identifier is email or username
  const isEmail = identifier.includes('@');
  const queryField = isEmail ? 'email' : 'username';

  // Find account
  const accountResult = await database.query(
    `SELECT * FROM accounts WHERE ${queryField} = $1`,
    [identifier]
  );

  if (accountResult.rows.length === 0) {
    return next(new ErrorHandler("Invalid credentials.", 401));
  }

  const account = accountResult.rows[0];

  // Check if account is active 
  if (account.account_status !== 'active') {
    if (account.account_status === 'pending_verification') {
      return next(new ErrorHandler("Please verify your email first.", 401));
    } else if (account.account_status === 'suspended') {
      return next(new ErrorHandler("Account is suspended.", 401));
    } else if (account.account_status === 'deactivated') {
      return next(new ErrorHandler("Account is deactivated.", 401));
    }
  }

  // Check password (skip for social accounts without password)
  if (account.password_hash) {
    const isPasswordMatch = await bcrypt.compare(password, account.password_hash);
    if (!isPasswordMatch) {
      // Increment failed login attempts
      await database.query(
        `UPDATE accounts SET 
          failed_login_attempts = failed_login_attempts + 1,
          last_failed_login = CURRENT_TIMESTAMP
        WHERE id = $1`,
        [account.id]
      );
      return next(new ErrorHandler("Invalid credentials.", 401));
    }
  } else {
    // Account was created via social auth, no password set
    return next(new ErrorHandler("Please use social login for this account.", 401));
  }

  // Reset failed login attempts on successful login
  await database.query(
    `UPDATE accounts SET 
      failed_login_attempts = 0,
      last_login_at = CURRENT_TIMESTAMP,
      login_count = login_count + 1,
      updated_at = CURRENT_TIMESTAMP
    WHERE id = $1`,
    [account.id]
  );

  // Set token expiration based on rememberMe
  const tokenOptions: any = {};
  if (rememberMe) {
    tokenOptions.expires = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days
  }

  generateToken(account, 200, "Logged in successfully.", res, tokenOptions);
});

// controller: logout
export const logout = catchAsyncError(async (req: Request, res: Response, next: NextFunction) => {
  res
    .status(200)
    .cookie("access_token", "", {
      expires: new Date(Date.now()),
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'strict'
    })
    .json({
      success: true,
      message: "Logged out successfully.",
    });
});

// controller: forgot password (sends reset token)
export const forgotPassword = catchAsyncError(async (req: Request, res: Response, next: NextFunction) => {
  const { email } = req.body;

  if (!email) {
    return next(new ErrorHandler("Email is required.", 400));
  }

  const accountResult = await database.query(
    `SELECT * FROM accounts WHERE email = $1 AND is_active = true`,
    [email]
  );

  if (accountResult.rows.length === 0) {
    // Don't reveal that email doesn't exist for security
    return res.status(200).json({
      success: true,
      message: "If an account exists with this email, you will receive a password reset link.",
    });
  }

  const account = accountResult.rows[0];

  // Generate reset token
  const resetToken = crypto.randomBytes(32).toString('hex');
  const resetPasswordToken = crypto
    .createHash('sha256')
    .update(resetToken)
    .digest('hex');
  const resetPasswordExpire = Date.now() + 15 * 60 * 1000; // 15 minutes

  // Save reset token to database
  await database.query(
    `UPDATE accounts SET 
      reset_password_token = $1,
      reset_password_expires_at = to_timestamp($2),
      updated_at = CURRENT_TIMESTAMP
    WHERE id = $3`,
    [resetPasswordToken, resetPasswordExpire / 1000, account.id]
  );

  // Create reset URL
  const frontendUrl = process.env.FRONTEND_URL;
  if (!frontendUrl) {
    return next(new ErrorHandler("Frontend URL not configured.", 500));
  }

  const resetUrl = `${frontendUrl}/reset-password/${resetToken}`;

  // Send reset email
  const message = resetEmailTemplate(resetUrl);

  try {
    await sendEmail({
      email: account.email,
      subject: "Password Reset Request - Toolffy",
      message,
    });
  } catch (error) {
    // Clear reset token if email fails
    await database.query(
      `UPDATE accounts SET 
        reset_password_token = NULL,
        reset_password_expires_at = NULL
      WHERE id = $1`,
      [account.id]
    );
    return next(new ErrorHandler("Failed to send reset email. Please try again.", 500));
  }

  res.status(200).json({
    success: true,
    message: "Password reset email sent successfully.",
  });
});

// controller: reset password
export const resetPassword = catchAsyncError(async (req: Request, res: Response, next: NextFunction) => {
  const { token } = req.params;
  const { password, confirmPassword } = req.body;

  if (!password || !confirmPassword) {
    return next(new ErrorHandler("Password and confirmation are required.", 400));
  }

  if (password !== confirmPassword) {
    return next(new ErrorHandler("Passwords do not match.", 400));
  }

  if (password.length < 8 || password.length > 16) {
    return next(
      new ErrorHandler("Password must be between 8 and 16 characters.", 400)
    );
  }

  // Hash the token
  const resetPasswordToken = crypto
    .createHash('sha256')
    .update(token)
    .digest('hex');

  // Find account with valid reset token
  const accountResult = await database.query(
    `SELECT * FROM accounts 
    WHERE reset_password_token = $1 
    AND reset_password_expires_at > CURRENT_TIMESTAMP
    AND is_active = true`,
    [resetPasswordToken]
  );

  if (accountResult.rows.length === 0) {
    return next(new ErrorHandler("Invalid or expired reset token.", 400));
  }

  const account = accountResult.rows[0];

  // Hash new password
  const hashedPassword = await bcrypt.hash(password, 12);

  // Update password and clear reset token
  await database.query(
    `UPDATE accounts SET 
      password_hash = $1,
      reset_password_token = NULL,
      reset_password_expires_at = NULL,
      failed_login_attempts = 0,
      updated_at = CURRENT_TIMESTAMP
    WHERE id = $2`,
    [hashedPassword, account.id]
  );

  // Get updated account
  const updatedAccountResult = await database.query(
    `SELECT * FROM accounts WHERE id = $1`,
    [account.id]
  );

  const updatedAccount = updatedAccountResult.rows[0];

  generateToken(updatedAccount, 200, "Password reset successfully.", res);
});

// controller: update password (logged in user)
export const updatePassword = catchAsyncError(async (req: Request, res: Response, next: NextFunction) => {
  const { currentPassword, newPassword, confirmNewPassword } = req.body;

  if (!currentPassword || !newPassword || !confirmNewPassword) {
    return next(new ErrorHandler("All password fields are required.", 400));
  }

  if (newPassword !== confirmNewPassword) {
    return next(new ErrorHandler("New passwords do not match.", 400));
  }

  if (newPassword.length < 8 || newPassword.length > 16) {
    return next(
      new ErrorHandler("Password must be between 8 and 16 characters.", 400)
    );
  }

  // Get account from request (set by auth middleware)
  const account = (req as any).user;

  // Verify current password
  const isPasswordMatch = await bcrypt.compare(currentPassword, account.password_hash);
  if (!isPasswordMatch) {
    return next(new ErrorHandler("Current password is incorrect.", 401));
  }

  // Hash new password
  const hashedPassword = await bcrypt.hash(newPassword, 12);

  // Update password
  await database.query(
    `UPDATE accounts SET 
      password_hash = $1,
      updated_at = CURRENT_TIMESTAMP
    WHERE id = $2`,
    [hashedPassword, account.id]
  );

  res.status(200).json({
    success: true,
    message: "Password updated successfully.",
  });
});

// controller: check username availability
export const checkUsername = catchAsyncError(async (req: Request, res: Response, next: NextFunction) => {
  const { username } = req.query;

  if (!username || typeof username !== 'string') {
    return next(new ErrorHandler("Username is required.", 400));
  }

  const usernameRegex = /^[a-zA-Z0-9_]{3,50}$/;
  if (!usernameRegex.test(username)) {
    return res.status(200).json({
      success: false,
      message: "Invalid username format.",
      available: false,
    });
  }

  const result = await database.query(
    `SELECT id FROM accounts WHERE username = $1`,
    [username]
  );

  res.status(200).json({
    success: true,
    available: result.rows.length === 0,
    message: result.rows.length === 0
      ? "Username is available."
      : "Username is already taken.",
  });
});

// controller: check email availability
export const checkEmail = catchAsyncError(async (req: Request, res: Response, next: NextFunction) => {
  const { email } = req.query;

  if (!email || typeof email !== 'string') {
    return next(new ErrorHandler("Email is required.", 400));
  }

  const emailRegex = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/;
  if (!emailRegex.test(email)) {
    return res.status(200).json({
      success: false,
      message: "Invalid email format.",
      available: false,
    });
  }

  const result = await database.query(
    `SELECT id FROM accounts WHERE email = $1`,
    [email]
  );

  res.status(200).json({
    success: true,
    available: result.rows.length === 0,
    message: result.rows.length === 0
      ? "Email is available."
      : "Email is already registered.",
  });
});
