// src/services/authService.ts
import * as bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import crypto from 'crypto';
import { userRepo, adminRepo } from './db/index.db';
import { UniqueConstraintError } from './db/errors.db';
import { getPool } from './db/connection.db';
import { logger } from './logger.server';
import { sendPasswordResetEmail } from './emailService.server';
import type { UserProfile } from '../types';
import { validatePasswordStrength } from '../utils/authUtils';

const JWT_SECRET = process.env.JWT_SECRET!;

class AuthService {
  async registerUser(
    email: string,
    password: string,
    fullName: string | undefined,
    avatarUrl: string | undefined,
    reqLog: any,
  ) {
    try {
      const saltRounds = 10;
      const hashedPassword = await bcrypt.hash(password, saltRounds);
      logger.info(`Hashing password for new user: ${email}`);

      // The createUser method in UserRepository now handles its own transaction.
      const newUser = await userRepo.createUser(
        email,
        hashedPassword,
        { full_name: fullName, avatar_url: avatarUrl },
        reqLog,
      );

      const userEmail = newUser.user.email;
      const userId = newUser.user.user_id;
      logger.info(`Successfully created new user in DB: ${userEmail} (ID: ${userId})`);

      // Use the new standardized logging function
      await adminRepo.logActivity(
        {
          userId: newUser.user.user_id,
          action: 'user_registered',
          displayText: `${userEmail} has registered.`,
          icon: 'user-plus',
        },
        reqLog,
      );

      return newUser;
    } catch (error: unknown) {
      if (error instanceof UniqueConstraintError) {
        // If the email is a duplicate, return a 409 Conflict status.
        throw error;
      }
      logger.error({ error }, `User registration route failed for email: ${email}.`);
      // Pass the error to the centralized handler
      throw error;
    }
  }

  async registerAndLoginUser(
    email: string,
    password: string,
    fullName: string | undefined,
    avatarUrl: string | undefined,
    reqLog: any,
  ): Promise<{ newUserProfile: UserProfile; accessToken: string; refreshToken: string }> {
    const newUserProfile = await this.registerUser(
      email,
      password,
      fullName,
      avatarUrl,
      reqLog,
    );
    const { accessToken, refreshToken } = await this.handleSuccessfulLogin(newUserProfile, reqLog);
    return { newUserProfile, accessToken, refreshToken };
  }

  generateAuthTokens(userProfile: UserProfile) {
    const payload = {
      user_id: userProfile.user.user_id,
      email: userProfile.user.email,
      role: userProfile.role,
    };
    const accessToken = jwt.sign(payload, JWT_SECRET, { expiresIn: '15m' });
    const refreshToken = crypto.randomBytes(64).toString('hex');

    return { accessToken, refreshToken };
  }

  async saveRefreshToken(userId: string, refreshToken: string, reqLog: any) {
    try {
      await userRepo.saveRefreshToken(userId, refreshToken, reqLog);
    } catch (tokenErr) {
      logger.error(
        { error: tokenErr },
        `Failed to save refresh token during login for user: ${userId}`,
      );
      throw tokenErr;
    }
  }

  async handleSuccessfulLogin(userProfile: UserProfile, reqLog: any) {
    const { accessToken, refreshToken } = this.generateAuthTokens(userProfile);
    await this.saveRefreshToken(userProfile.user.user_id, refreshToken, reqLog);
    return { accessToken, refreshToken };
  }

  async resetPassword(email: string, reqLog: any) {
    try {
      logger.debug(`[API /forgot-password] Received request for email: ${email}`);
      const user = await userRepo.findUserByEmail(email, reqLog);
      let token: string | undefined;
      logger.debug(
        { user: user ? { user_id: user.user_id, email: user.email } : 'NOT FOUND' },
        `[API /forgot-password] Database search result for ${email}:`,
      );

      if (user) {
        token = crypto.randomBytes(32).toString('hex');
        const saltRounds = 10;
        const tokenHash = await bcrypt.hash(token, saltRounds);
        const expiresAt = new Date(Date.now() + 3600000); // 1 hour

        await userRepo.createPasswordResetToken(user.user_id, tokenHash, expiresAt, reqLog);

        const resetLink = `${process.env.FRONTEND_URL}/reset-password/${token}`;

        try {
          await sendPasswordResetEmail(email, resetLink, reqLog);
        } catch (emailError) {
          logger.error({ emailError }, `Email send failure during password reset for user`);
        }
      } else {
        logger.warn(`Password reset requested for non-existent email: ${email}`);
      }

      return token;
    } catch (error) {
      logger.error({ error }, `An error occurred during /forgot-password for email: ${email}`);
      throw error;
    }
  }

  async updatePassword(token: string, newPassword: string, reqLog: any) {
    try {
      const validTokens = await userRepo.getValidResetTokens(reqLog);
      let tokenRecord;
      for (const record of validTokens) {
        const isMatch = await bcrypt.compare(token, record.token_hash);
        if (isMatch) {
          tokenRecord = record;
          break;
        }
      }

      if (!tokenRecord) {
        return null;
      }

      const saltRounds = 10;
      const hashedPassword = await bcrypt.hash(newPassword, saltRounds);

      await userRepo.updateUserPassword(tokenRecord.user_id, hashedPassword, reqLog);
      await userRepo.deleteResetToken(tokenRecord.token_hash, reqLog);

      // Log this security event after a successful password reset.
      await adminRepo.logActivity(
        {
          userId: tokenRecord.user_id,
          action: 'password_reset',
          displayText: `User ID ${tokenRecord.user_id} has reset their password.`,
          icon: 'key',
          details: { source_ip: null },
        },
        reqLog,
      );

      return true;
    } catch (error) {
      logger.error({ error }, `An error occurred during password reset.`);
      throw error;
    }
  }

  async getUserByRefreshToken(refreshToken: string, reqLog: any) {
    try {
      const basicUser = await userRepo.findUserByRefreshToken(refreshToken, reqLog);
      if (!basicUser) {
        return null;
      }
      const userProfile = await userRepo.findUserProfileById(basicUser.user_id, reqLog);
      return userProfile;
    } catch (error) {
      logger.error({ error }, 'An error occurred during /refresh-token.');
      throw error;
    }
  }

  async logout(refreshToken: string, reqLog: any) {
    try {
      await userRepo.deleteRefreshToken(refreshToken, reqLog);
    } catch (err: any) {
      logger.error({ error: err }, 'Failed to delete refresh token from DB during logout.');
      throw err;
    }
  }

  async refreshAccessToken(refreshToken: string, reqLog: any): Promise<{ accessToken: string } | null> {
    const user = await this.getUserByRefreshToken(refreshToken, reqLog);
    if (!user) {
      return null;
    }
    const { accessToken } = this.generateAuthTokens(user);
    return { accessToken };
  }
}

export const authService = new AuthService();