import { Request, Response } from 'express';
import prisma from '../config/database';
import { hashPassword, comparePassword } from '../utils/hash.utils';
import { signAccessToken, signRefreshToken, verifyRefreshToken } from '../utils/jwt.utils';
import { sendSuccess, sendError } from '../utils/response.utils';
import { AuthenticatedRequest } from '../middleware/auth.middleware';
import crypto from 'crypto';

// Utility to generate 6-digit numeric OTP
const generateNumericOtp = (): string => {
  return Math.floor(100000 + Math.random() * 900000).toString();
};

// 1. Customer Registration
export const register = async (req: Request, res: Response) => {
  try {
    const { email, password, firstName, lastName, phone } = req.body;

    const existingUser = await prisma.user.findUnique({ where: { email } });
    if (existingUser) {
      return sendError(res, 'Email is already registered', null, 400);
    }

    const hashedPassword = await hashPassword(password);
    const user = await prisma.user.create({
      data: {
        email,
        password: hashedPassword,
        firstName,
        lastName,
        phone,
        status: 'ACTIVE',
        isEmailVerified: false,
      },
    });

    // Create Email Verification OTP
    const otpCode = generateNumericOtp();
    await prisma.otpToken.create({
      data: {
        email,
        code: otpCode,
        purpose: 'EMAIL_VERIFICATION',
        expiresAt: new Date(Date.now() + 15 * 60 * 1000), // 15 mins
      },
    });

    // Create Active Session
    const session = await prisma.session.create({
      data: {
        userId: user.id,
        ipAddress: req.ip,
        userAgent: req.get('user-agent'),
      },
    });

    const tokenPayload = { userId: user.id, email: user.email, role: 'CUSTOMER' as const, sessionId: session.id };
    const token = signAccessToken(tokenPayload, false);
    const refreshToken = signRefreshToken(tokenPayload, false);

    await prisma.refreshToken.create({
      data: {
        token: refreshToken,
        userId: user.id,
        ipAddress: req.ip,
        userAgent: req.get('user-agent'),
        expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
      },
    });

    return sendSuccess(
      res,
      'Registration successful. Please verify your email with the OTP sent.',
      {
        user: {
          id: user.id,
          email: user.email,
          firstName: user.firstName,
          lastName: user.lastName,
          role: 'CUSTOMER',
          isEmailVerified: user.isEmailVerified,
        },
        token,
        refreshToken,
        otpCode, // Returned for dev testing
      },
      201
    );
  } catch (error: any) {
    return sendError(res, 'Error registering user', error.message, 500);
  }
};

// 2. Customer & General Login (with Remember Me)
export const login = async (req: Request, res: Response) => {
  try {
    const { email, password, rememberMe } = req.body;

    const user = await prisma.user.findUnique({ where: { email } });
    if (!user) {
      return sendError(res, 'Invalid email or password', null, 401);
    }

    if (user.status !== 'ACTIVE') {
      return sendError(res, `Account is currently ${user.status.toLowerCase()}`, null, 403);
    }

    const isMatch = await comparePassword(password, user.password);
    if (!isMatch) {
      return sendError(res, 'Invalid email or password', null, 401);
    }

    // Create session
    const session = await prisma.session.create({
      data: {
        userId: user.id,
        ipAddress: req.ip,
        userAgent: req.get('user-agent'),
      },
    });

    const tokenPayload = { userId: user.id, email: user.email, role: 'CUSTOMER' as const, sessionId: session.id };
    const token = signAccessToken(tokenPayload, rememberMe);
    const refreshToken = signRefreshToken(tokenPayload, rememberMe);

    await prisma.refreshToken.create({
      data: {
        token: refreshToken,
        userId: user.id,
        ipAddress: req.ip,
        userAgent: req.get('user-agent'),
        expiresAt: new Date(Date.now() + (rememberMe ? 30 : 7) * 24 * 60 * 60 * 1000),
      },
    });

    return sendSuccess(res, 'Login successful', {
      user: {
        id: user.id,
        email: user.email,
        firstName: user.firstName,
        lastName: user.lastName,
        role: 'CUSTOMER',
        isEmailVerified: user.isEmailVerified,
      },
      token,
      refreshToken,
    });
  } catch (error: any) {
    return sendError(res, 'Error logging in', error.message, 500);
  }
};

// 3. Admin Login
export const adminLogin = async (req: Request, res: Response) => {
  try {
    const { email, password } = req.body;

    const admin = await prisma.admin.findUnique({ where: { email } });
    if (!admin) {
      return sendError(res, 'Invalid admin credentials', null, 401);
    }

    const isMatch = await comparePassword(password, admin.password);
    if (!isMatch) {
      return sendError(res, 'Invalid admin credentials', null, 401);
    }

    const session = await prisma.session.create({
      data: {
        adminId: admin.id,
        ipAddress: req.ip,
        userAgent: req.get('user-agent'),
      },
    });

    const tokenPayload = { adminId: admin.id, email: admin.email, role: 'ADMIN' as const, sessionId: session.id };
    const token = signAccessToken(tokenPayload, false);
    const refreshToken = signRefreshToken(tokenPayload, false);

    await prisma.refreshToken.create({
      data: {
        token: refreshToken,
        adminId: admin.id,
        ipAddress: req.ip,
        userAgent: req.get('user-agent'),
        expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
      },
    });

    return sendSuccess(res, 'Admin authentication successful', {
      admin: {
        id: admin.id,
        email: admin.email,
        name: admin.name,
        role: admin.role,
      },
      token,
      refreshToken,
    });
  } catch (error: any) {
    return sendError(res, 'Admin login failed', error.message, 500);
  }
};

// 4. Send Email Verification OTP
export const sendVerificationOtp = async (req: Request, res: Response) => {
  try {
    const { email } = req.body;
    const user = await prisma.user.findUnique({ where: { email } });
    if (!user) {
      return sendError(res, 'User email not found', null, 404);
    }

    if (user.isEmailVerified) {
      return sendError(res, 'Email is already verified', null, 400);
    }

    const otpCode = generateNumericOtp();
    await prisma.otpToken.create({
      data: {
        email,
        code: otpCode,
        purpose: 'EMAIL_VERIFICATION',
        expiresAt: new Date(Date.now() + 15 * 60 * 1000),
      },
    });

    return sendSuccess(res, 'Verification OTP sent successfully', { otpCode });
  } catch (error: any) {
    return sendError(res, 'Error sending OTP', error.message, 500);
  }
};

// 5. Verify OTP (Email Verification or Password Reset)
export const verifyOtp = async (req: Request, res: Response) => {
  try {
    const { email, code, purpose } = req.body;

    const otpRecord = await prisma.otpToken.findFirst({
      where: {
        email,
        code,
        purpose,
        isUsed: false,
        expiresAt: { gt: new Date() },
      },
      orderBy: { createdAt: 'desc' },
    });

    if (!otpRecord) {
      return sendError(res, 'Invalid or expired OTP code', null, 400);
    }

    await prisma.otpToken.update({
      where: { id: otpRecord.id },
      data: { isUsed: true },
    });

    if (purpose === 'EMAIL_VERIFICATION') {
      await prisma.user.update({
        where: { email },
        data: { isEmailVerified: true },
      });
    }

    return sendSuccess(res, 'OTP verified successfully', { email, purpose, verified: true });
  } catch (error: any) {
    return sendError(res, 'OTP verification failed', error.message, 500);
  }
};

// 6. Forgot Password (Request Password Reset)
export const forgotPassword = async (req: Request, res: Response) => {
  try {
    const { email } = req.body;

    const user = await prisma.user.findUnique({ where: { email } });
    if (!user) {
      // Return success for security (prevent email enumeration)
      return sendSuccess(res, 'If account exists, password reset instructions have been generated.');
    }

    const otpCode = generateNumericOtp();
    const resetToken = crypto.randomBytes(32).toString('hex');

    await Promise.all([
      prisma.otpToken.create({
        data: {
          email,
          code: otpCode,
          purpose: 'PASSWORD_RESET',
          expiresAt: new Date(Date.now() + 15 * 60 * 1000),
        },
      }),
      prisma.passwordResetToken.create({
        data: {
          email,
          token: resetToken,
          expiresAt: new Date(Date.now() + 60 * 60 * 1000), // 1 hour
        },
      }),
    ]);

    return sendSuccess(res, 'Password reset OTP and token generated.', {
      otpCode,
      resetToken,
    });
  } catch (error: any) {
    return sendError(res, 'Error processing forgot password', error.message, 500);
  }
};

// 7. Reset Password
export const resetPassword = async (req: Request, res: Response) => {
  try {
    const { email, token, newPassword } = req.body;

    // Check if token matches reset token or OTP code
    const resetTokenRecord = await prisma.passwordResetToken.findFirst({
      where: { email, token, isUsed: false, expiresAt: { gt: new Date() } },
    });

    const otpRecord = !resetTokenRecord
      ? await prisma.otpToken.findFirst({
          where: { email, code: token, purpose: 'PASSWORD_RESET', isUsed: false, expiresAt: { gt: new Date() } },
        })
      : null;

    if (!resetTokenRecord && !otpRecord) {
      return sendError(res, 'Invalid or expired password reset token / OTP', null, 400);
    }

    if (resetTokenRecord) {
      await prisma.passwordResetToken.update({
        where: { id: resetTokenRecord.id },
        data: { isUsed: true },
      });
    }

    if (otpRecord) {
      await prisma.otpToken.update({
        where: { id: otpRecord.id },
        data: { isUsed: true },
      });
    }

    const hashedPassword = await hashPassword(newPassword);
    await prisma.user.update({
      where: { email },
      data: { password: hashedPassword },
    });

    return sendSuccess(res, 'Password reset successful. You can now login with your new password.');
  } catch (error: any) {
    return sendError(res, 'Failed to reset password', error.message, 500);
  }
};

// 8. Google OAuth Login
export const googleLogin = async (req: Request, res: Response) => {
  try {
    const { idToken } = req.body;

    // Simulated Google OAuth profile verification
    // In production, user info is retrieved via OAuth2Client.verifyIdToken(idToken)
    const googleUser = {
      email: `google_${Date.now()}@example.com`,
      firstName: 'Google',
      lastName: 'User',
      avatarUrl: 'https://lh3.googleusercontent.com/a/default-user',
    };

    let user = await prisma.user.findUnique({ where: { email: googleUser.email } });

    if (!user) {
      const dummyPassword = await hashPassword(crypto.randomBytes(16).toString('hex'));
      user = await prisma.user.create({
        data: {
          email: googleUser.email,
          password: dummyPassword,
          firstName: googleUser.firstName,
          lastName: googleUser.lastName,
          avatarUrl: googleUser.avatarUrl,
          isEmailVerified: true,
        },
      });
    }

    const session = await prisma.session.create({
      data: {
        userId: user.id,
        ipAddress: req.ip,
        userAgent: req.get('user-agent'),
      },
    });

    const tokenPayload = { userId: user.id, email: user.email, role: 'CUSTOMER' as const, sessionId: session.id };
    const token = signAccessToken(tokenPayload, true);
    const refreshToken = signRefreshToken(tokenPayload, true);

    await prisma.refreshToken.create({
      data: {
        token: refreshToken,
        userId: user.id,
        ipAddress: req.ip,
        userAgent: req.get('user-agent'),
        expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
      },
    });

    return sendSuccess(res, 'Google authentication successful', {
      user: {
        id: user.id,
        email: user.email,
        firstName: user.firstName,
        lastName: user.lastName,
        avatarUrl: user.avatarUrl,
        role: 'CUSTOMER',
      },
      token,
      refreshToken,
    });
  } catch (error: any) {
    return sendError(res, 'Google login failed', error.message, 500);
  }
};

// 9. Session Refresh Token Handler
export const refreshTokenHandler = async (req: Request, res: Response) => {
  try {
    const { refreshToken } = req.body;
    if (!refreshToken) {
      return sendError(res, 'Refresh token is required', null, 400);
    }

    const storedToken = await prisma.refreshToken.findUnique({
      where: { token: refreshToken },
    });

    if (!storedToken || storedToken.revoked || storedToken.expiresAt < new Date()) {
      return sendError(res, 'Invalid or expired refresh token', null, 401);
    }

    const payload = verifyRefreshToken(refreshToken);
    const newToken = signAccessToken({
      userId: payload.userId,
      adminId: payload.adminId,
      email: payload.email,
      role: payload.role,
      sessionId: payload.sessionId,
    });

    return sendSuccess(res, 'Token refreshed successfully', { token: newToken });
  } catch (error: any) {
    return sendError(res, 'Token refresh failed', error.message, 401);
  }
};

// 10. Get Active Sessions
export const getActiveSessions = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const userId = req.user?.userId;
    const adminId = req.user?.adminId;

    const sessions = await prisma.session.findMany({
      where: {
        ...(userId && { userId }),
        ...(adminId && { adminId }),
        isActive: true,
      },
      orderBy: { lastSeen: 'desc' },
    });

    return sendSuccess(res, 'Active sessions retrieved', sessions);
  } catch (error: any) {
    return sendError(res, 'Error fetching sessions', error.message, 500);
  }
};

// 11. Logout Single Session
export const logout = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const { refreshToken } = req.body;
    if (refreshToken) {
      await prisma.refreshToken.updateMany({
        where: { token: refreshToken },
        data: { revoked: true },
      });
    }

    if (req.user?.sessionId) {
      await prisma.session.update({
        where: { id: req.user.sessionId },
        data: { isActive: false },
      });
    }

    return sendSuccess(res, 'Logged out successfully');
  } catch (error: any) {
    return sendError(res, 'Error logging out', error.message, 500);
  }
};

// 12. Logout All Devices
export const logoutAllDevices = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const userId = req.user?.userId;
    const adminId = req.user?.adminId;

    await Promise.all([
      prisma.refreshToken.updateMany({
        where: {
          ...(userId && { userId }),
          ...(adminId && { adminId }),
        },
        data: { revoked: true },
      }),
      prisma.session.updateMany({
        where: {
          ...(userId && { userId }),
          ...(adminId && { adminId }),
        },
        data: { isActive: false },
      }),
    ]);

    return sendSuccess(res, 'Successfully logged out from all devices');
  } catch (error: any) {
    return sendError(res, 'Failed to logout all devices', error.message, 500);
  }
};

// 13. Update Profile
export const updateProfile = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const userId = req.user?.userId;
    if (!userId) {
      return sendError(res, 'User ID not found in session', null, 401);
    }

    const { firstName, lastName, email, phone } = req.body;

    const user = await prisma.user.findUnique({ where: { id: userId } });
    if (!user) {
      return sendError(res, 'User not found', null, 404);
    }

    if (email && email !== user.email) {
      const emailExists = await prisma.user.findUnique({ where: { email } });
      if (emailExists) {
        return sendError(res, 'Email address is already in use by another account', null, 400);
      }
    }

    const updatedUser = await prisma.user.update({
      where: { id: userId },
      data: {
        ...(firstName !== undefined && { firstName }),
        ...(lastName !== undefined && { lastName }),
        ...(email !== undefined && { email }),
        ...(phone !== undefined && { phone }),
      },
    });

    return sendSuccess(res, 'Profile updated successfully', {
      user: {
        id: updatedUser.id,
        email: updatedUser.email,
        firstName: updatedUser.firstName,
        lastName: updatedUser.lastName,
        phone: updatedUser.phone,
        role: 'CUSTOMER',
        avatarUrl: updatedUser.avatarUrl,
        isEmailVerified: updatedUser.isEmailVerified,
      },
    });
  } catch (error: any) {
    return sendError(res, 'Failed to update profile', error.message, 500);
  }
};

