import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';

import { v4 as uuidv4 } from 'uuid';
import { db } from '../application/database';
import { ResponseError } from '../error/response-error';
import { Pageable } from '../model/page';
import {
  UserResponse,
  toUserResponse,
  UpdateUserRequest,
  UpdatePasswordRequest,
  UpdateCurrentUserRequest,
  UserQueryParams,
  CreateUserRequest,
  User,
} from '../model/user-model';
import { Validation } from '../validation/validation';
import { UserValidation } from '../validation/user-validation';

export class UserService {
  static async create(request: CreateUserRequest): Promise<UserResponse> {
    // Validate the create request
    const createRequest = Validation.validate(UserValidation.CREATE, request);

    // Check if the email already exists
    const existingUser = await db.queryOne<{ id: string }>('SELECT id FROM user WHERE email = ? LIMIT 1', [
      createRequest.email,
    ]);

    if (existingUser) {
      throw new ResponseError(400, 'Email sudah terdaftar');
    }

    // Hash the password before storing it
    const hashedPassword = await bcrypt.hash(createRequest.password, 10);

    // Generate a new UUID for the user ID
    const userId = uuidv4();

    // Insert the new user into the database
    await db.query(
      'INSERT INTO user (id, full_name, email, whatsapp_number, password, role) VALUES (?, ?, ?, ?, ?, ?)',
      [
        userId,
        createRequest.full_name,
        createRequest.email,
        createRequest.whatsapp_number,
        hashedPassword,
        createRequest.role,
      ]
    );

    // Retrieve the newly created user by the email
    const user = await db.queryOne<User>('SELECT * FROM user WHERE id = ? LIMIT 1', [userId]);

    if (!user) {
      throw new ResponseError(500, 'Gagal melakukan registrasi user');
    }

    return toUserResponse(user);
  }

  static async getAll(queryParams: UserQueryParams): Promise<Pageable<UserResponse>> {
    // Validate the query params
    const queryRequest = Validation.validate(UserValidation.QUERY, queryParams);

    // Calculate the skip value
    const skip = (queryRequest.page - 1) * queryRequest.limit;

    // Create the WHERE clause
    let filterQuery = 'WHERE 1 = 1';
    const filterParams: any[] = [];

    if (queryRequest.full_name) {
      filterQuery += ' AND full_name LIKE ?';
      filterParams.push(`%${queryRequest.full_name}%`);
    }
    if (queryRequest.email) {
      filterQuery += ' AND email LIKE ?';
      filterParams.push(`%${queryRequest.email}%`);
    }
    if (queryRequest.whatsapp_number) {
      filterQuery += ' AND whatsapp_number LIKE ?';
      filterParams.push(`%${queryRequest.whatsapp_number}%`);
    }
    if (queryRequest.role) {
      filterQuery += ' AND role = ?';
      filterParams.push(queryRequest.role);
    }

    // Create the ORDER BY clause
    const sortField = queryRequest.sort || 'created_at';
    const sortOrder = queryRequest.order || 'desc';

    // Execute the query
    const users = await db.query<User>(
      `SELECT * FROM user ${filterQuery} ORDER BY ${sortField} ${sortOrder} LIMIT ? OFFSET ?`,
      [...filterParams, queryRequest.limit, skip]
    );

    // Get the total count
    const count = await db.queryOne<{ total: number }>(
      `SELECT COUNT(*) AS total FROM user ${filterQuery}`,
      filterParams
    );

    if (!count) {
      throw new ResponseError(500, 'Failed to get total count');
    }

    // Return the pageable response
    return {
      data: users.map(toUserResponse),
      pagination: {
        total: count.total,
        current_page: queryRequest.page,
        total_pages: Math.ceil(count.total / queryRequest.limit),
        limit: queryRequest.limit,
      },
    };
  }

  static async get(id: string): Promise<UserResponse> {
    // Execute the query
    const user = await db.queryOne<User>('SELECT * FROM user WHERE id = ? LIMIT 1', [id]);

    if (!user) {
      throw new ResponseError(404, 'User tidak ditemukan');
    }

    return toUserResponse(user);
  }

  static async updateCurrent(user: User, request: UpdateCurrentUserRequest): Promise<UserResponse> {
    // Validate the update request
    const updateRequest = Validation.validate(UserValidation.UPDATE_CURRENT, request);

    // Check if the email already exists
    if (updateRequest.email) {
      const existingUser = await db.queryOne<{ id: string }>('SELECT id FROM user WHERE email = ? LIMIT 1', [
        updateRequest.email,
      ]);

      if (existingUser && existingUser.id !== user.id) {
        throw new ResponseError(400, 'Email sudah terdaftar');
      }
    }

    // Initialize the query parameters
    const setClauses: string[] = [];
    const setParams: any[] = [];

    // Build the SET clause
    if (updateRequest.full_name) {
      setClauses.push('full_name = ?');
      setParams.push(updateRequest.full_name);
    }
    if (updateRequest.email) {
      setClauses.push('email = ?');
      setParams.push(updateRequest.email);
    }
    if (updateRequest.whatsapp_number) {
      setClauses.push('whatsapp_number = ?');
      setParams.push(updateRequest.whatsapp_number);
    }

    // Update the user in the database
    await db.query(`UPDATE user SET ${setClauses.join(', ')} WHERE id = ?`, [...setParams, user.id]);

    // Retrieve the updated user
    const updatedUser = await db.queryOne<User>('SELECT * FROM user WHERE id = ? LIMIT 1', [user.id]);

    if (!updatedUser) {
      throw new ResponseError(500, 'Failed to update user');
    }

    // Generate a new JWT token
    const token = jwt.sign(
      {
        id: updatedUser.id,
        email: updatedUser.email,
        full_name: updatedUser.full_name,
        whatsapp_number: updatedUser.whatsapp_number,
        role: updatedUser.role,
      },
      String(process.env.JWT_SECRET),
      {
        expiresIn: '24h',
      }
    );

    // Format the user response
    const response = toUserResponse(updatedUser);
    response.token = token;
    return response;
  }

  static async updateCurrentPassword(user: User, request: UpdatePasswordRequest): Promise<UserResponse> {
    // Validate the update password request
    const updatePasswordRequest = Validation.validate(UserValidation.UPDATE_PASSWORD, request);

    // Retrieve the old password
    const oldUserPassword = await db.queryOne<{ password: string }>('SELECT password FROM user WHERE id = ? LIMIT 1', [
      user.id,
    ]);

    if (!oldUserPassword) {
      throw new ResponseError(404, 'User tidak ditemukan');
    }

    // Compare the old password
    const passwordMatch = await bcrypt.compare(updatePasswordRequest.old_password, oldUserPassword.password);

    if (!passwordMatch) {
      throw new ResponseError(400, 'Password lama salah');
    }

    // Hash the new password
    const hashedPassword = await bcrypt.hash(updatePasswordRequest.new_password, 10);

    // Update the password in the database
    await db.query('UPDATE user SET password = ? WHERE id = ?', [hashedPassword, user.id]);

    // Retrieve the updated user
    const updatedUser = await db.queryOne<User>('SELECT * FROM user WHERE id = ? LIMIT 1', [user.id]);

    if (!updatedUser) {
      throw new ResponseError(500, 'Failed to update password');
    }

    return toUserResponse(updatedUser);
  }

  static async update(id: string, request: UpdateUserRequest): Promise<UserResponse> {
    // Validate the update request
    const updateRequest = Validation.validate(UserValidation.UPDATE, request);

    // Check if the email already exists
    if (updateRequest.email) {
      const existingUser = await db.queryOne<{ id: string }>('SELECT id FROM user WHERE email = ? LIMIT 1', [
        updateRequest.email,
      ]);

      if (existingUser && existingUser.id !== id) {
        throw new ResponseError(400, 'Email sudah terdaftar');
      }
    }

    // Initialize the query parameters
    const setClauses: string[] = [];
    const setParams: any[] = [];

    // Build the SET clause
    if (updateRequest.full_name) {
      setClauses.push('full_name = ?');
      setParams.push(updateRequest.full_name);
    }
    if (updateRequest.email) {
      setClauses.push('email = ?');
      setParams.push(updateRequest.email);
    }
    if (updateRequest.whatsapp_number) {
      setClauses.push('whatsapp_number = ?');
      setParams.push(updateRequest.whatsapp_number);
    }
    if (updateRequest.role) {
      setClauses.push('role = ?');
      setParams.push(updateRequest.role);
    }
    
    // Add the ID parameter
    setParams.push(id);

    // Execute the query
    await db.query(`UPDATE user SET ${setClauses.join(', ')} WHERE id = ?`, setParams);

    // Retrieve the updated user
    const updatedUser = await db.queryOne<User>('SELECT * FROM user WHERE id = ? LIMIT 1', [id]);

    if (!updatedUser) {
      throw new ResponseError(500, 'Failed to update user');
    }

    return toUserResponse(updatedUser);
  }

  static async delete(id: string): Promise<UserResponse> {
    // Retrieve the user to be deleted
    const user = await db.queryOne<User>('SELECT * FROM user WHERE id = ? LIMIT 1', [id]);

    if (!user) {
      throw new ResponseError(404, 'User tidak ditemukan');
    }

    // Delete the user from the database
    await db.query('DELETE FROM user WHERE id = ?', [id]);

    return toUserResponse(user);
  }

  static async upgradeToMitra(id: string): Promise<UserResponse> {
    // Retrieve the user to be upgraded
    const user = await db.queryOne<User>('SELECT * FROM user WHERE id = ? LIMIT 1', [id]);

    if (!user) {
      throw new ResponseError(404, 'User tidak ditemukan');
    }

    if (user.role !== 'USER') {
      throw new ResponseError(400, 'User sudah menjadi mitra');
    }

    // Upgrade the user to MITRA
    await db.query('UPDATE user SET role = ? WHERE id = ?', ['MITRA', id]);

    // Retrieve the updated user
    const updatedUser = await db.queryOne<User>('SELECT * FROM user WHERE id = ? LIMIT 1', [id]);

    if (!updatedUser) {
      throw new ResponseError(500, 'Failed to upgrade user');
    }

    // Generate a new JWT token
    const token = jwt.sign(
      {
        id: updatedUser.id,
        email: updatedUser.email,
        full_name: updatedUser.full_name,
        whatsapp_number: updatedUser.whatsapp_number,
        role: updatedUser.role,
      },
      String(process.env.JWT_SECRET),
      {
        expiresIn: '24h',
      }
    );

    // Format the user response
    const response = toUserResponse(updatedUser);
    response.token = token;
    return response;
  }
}
