const AdminRepository = require('../../repositories/admin/AdminRepository');
const UserDocumentRepository = require('../../repositories/documents/UserDocumentRepository');
const UserRepository = require('../../repositories/user/UserRepository');
const { s3 } = require('../../utils/exoscaleUploader');
const { GetObjectCommand, ListObjectsV2Command } = require('@aws-sdk/client-s3');
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner');
const { DeleteObjectCommand } = require('@aws-sdk/client-s3');
const pidusage = require('pidusage');
const os = require('os');
const { logger } = require('../../middleware/logger');

class AdminService {
  static async getUserStats(unitOfWork) {
    logger.info('AdminService.getUserStats:start');
    try {
      const stats = await AdminRepository.getUserStats(unitOfWork.connection);
      logger.info('AdminService.getUserStats:success', stats);
      return stats;
    } catch (error) {
      logger.error('AdminService.getUserStats:error', { error: error.message });
      throw error;
    }
  }

  static async getUserList(unitOfWork) {
    logger.info('AdminService.getUserList:start');
    try {
      const list = await AdminRepository.getUserList(unitOfWork.connection);
      logger.info('AdminService.getUserList:success', { count: list.length });
      return list;
    } catch (error) {
      logger.error('AdminService.getUserList:error', { error: error.message });
      throw error;
    }
  }

  static async getVerificationPendingUsers(unitOfWork) {
    logger.info('AdminService.getVerificationPendingUsers:start');
    try {
      const users = await AdminRepository.getVerificationPendingUsers(unitOfWork.connection);
      logger.info('AdminService.getVerificationPendingUsers:success', { count: users.length });
      return users;
    } catch (error) {
      logger.error('AdminService.getVerificationPendingUsers:error', { error: error.message });
      throw error;
    }
  }

  static async getUnverifiedUsers(unitOfWork) {
    logger.info('AdminService.getUnverifiedUsers:start');
    try {
      const users = await AdminRepository.getUnverifiedUsers(unitOfWork.connection);
      logger.info('AdminService.getUnverifiedUsers:success', { count: users.length });
      return users;
    } catch (error) {
      logger.error('AdminService.getUnverifiedUsers:error', { error: error.message });
      throw error;
    }
  }

  static async verifyUser(unitOfWork, userId, permissions) {
    logger.info('AdminService.verifyUser:start', { userId, permissions });
    try {
      // 1. Fetch user
      const userRepo = new UserRepository(unitOfWork);
      const user = await userRepo.findUserByEmailOrId(Number(userId));
      if (!user) throw new Error('User not found');
      logger.info('AdminService.verifyUser:user_fetched', { userId });

      // 2. Fetch documents and contracts
      const documents = await AdminRepository.getUserDocuments(unitOfWork.connection, userId);
      const contracts = await AdminRepository.getUserContracts(unitOfWork.connection, userId);
      logger.info('AdminService.verifyUser:documents_fetched', { userId, documentsCount: documents.length, contractsCount: contracts.length });

      // 3. Fetch ID document metadata
      const idDocs = await AdminRepository.getUserIdDocuments(unitOfWork.connection, userId);
      logger.info('AdminService.verifyUser:id_docs_fetched', { userId, idDocsCount: idDocs.length });

      // 4. Generate signed URLs for front/back of ID documents
      const idDocumentsWithUrls = await Promise.all(
        idDocs.map(async doc => {
          let frontUrl = null, backUrl = null;
          if (doc.front_object_storage_id) {
            try {
              const command = new GetObjectCommand({
                Bucket: process.env.EXOSCALE_BUCKET,
                Key: doc.front_object_storage_id
              });
              frontUrl = await getSignedUrl(s3, command, { expiresIn: 900 });
            } catch (err) { frontUrl = null; }
          }
          if (doc.back_object_storage_id) {
            try {
              const command = new GetObjectCommand({
                Bucket: process.env.EXOSCALE_BUCKET,
                Key: doc.back_object_storage_id
              });
              backUrl = await getSignedUrl(s3, command, { expiresIn: 900 });
            } catch (err) { backUrl = null; }
          }
          return { ...doc, frontUrl, backUrl };
        })
      );
      logger.info('AdminService.verifyUser:id_docs_urls_generated', { userId, idDocsCount: idDocumentsWithUrls.length });

      // 5. Update user_status
      await AdminRepository.verifyUser(unitOfWork.connection, userId);
      logger.info('AdminService.verifyUser:user_status_updated', { userId });

      // 6. Assign permissions
      if (permissions.length > 0) {
        await AdminRepository.assignPermissions(unitOfWork.connection, userId, permissions);
        logger.info('AdminService.verifyUser:permissions_assigned', { userId, permissions });
      }

      // 7. Return updated user data
      logger.info('AdminService.verifyUser:success', { userId });
      return {
        message: 'User verified and permissions updated',
        user: user.getPublicData(),
        documents,
        contracts,
        idDocuments: idDocumentsWithUrls
      };
    } catch (error) {
      logger.error('AdminService.verifyUser:error', { userId, error: error.message });
      throw error;
    }
  }

  static async getFullUserAccountDetails(unitOfWork, userId) {
    logger.info('AdminService.getFullUserAccountDetails:start', { userId });
    try {
      const user = await AdminRepository.getUserById(unitOfWork.connection, userId);
      if (!user) throw new Error('User not found');

      const personalProfile = await AdminRepository.getPersonalProfile(unitOfWork.connection, userId);
      const companyProfile = await AdminRepository.getCompanyProfile(unitOfWork.connection, userId);
      const permissions = await AdminRepository.getUserPermissions(unitOfWork.connection, userId);

      logger.info('AdminService.getFullUserAccountDetails:success', { userId });
      return { user, personalProfile, companyProfile, permissions };
    } catch (error) {
      logger.error('AdminService.getFullUserAccountDetails:error', { userId, error: error.message });
      throw error;
    }
  }

  static async getDetailedUserInfo(unitOfWork, userId) {
    logger.info('AdminService.getDetailedUserInfo:start', { userId });
    try {
      // Get basic user info
      const user = await AdminRepository.getUserById(unitOfWork.connection, userId);
      if (!user) throw new Error('User not found');

      // Get profiles
      const personalProfile = await AdminRepository.getPersonalProfile(unitOfWork.connection, userId);
      const companyProfile = await AdminRepository.getCompanyProfile(unitOfWork.connection, userId);
      
      // Get permissions
      const permissions = await AdminRepository.getUserPermissions(unitOfWork.connection, userId);
      
      // Get user status
      const [statusRows] = await unitOfWork.connection.query(
        `SELECT * FROM user_status WHERE user_id = ? LIMIT 1`, [userId]
      );
      const userStatus = statusRows.length ? statusRows[0] : null;

      // Get documents
      const documents = await AdminRepository.getUserDocuments(unitOfWork.connection, userId);
      const contracts = await AdminRepository.getUserContracts(unitOfWork.connection, userId);
      
      // Get ID documents with signed URLs
      const idDocs = await AdminRepository.getUserIdDocuments(unitOfWork.connection, userId);
      const idDocumentsWithUrls = await Promise.all(
        idDocs.map(async doc => {
          let frontUrl = null, backUrl = null;
          if (doc.front_object_storage_id) {
            try {
              const command = new GetObjectCommand({
                Bucket: process.env.EXOSCALE_BUCKET,
                Key: doc.front_object_storage_id
              });
              frontUrl = await getSignedUrl(s3, command, { expiresIn: 900 });
            } catch (err) { frontUrl = null; }
          }
          if (doc.back_object_storage_id) {
            try {
              const command = new GetObjectCommand({
                Bucket: process.env.EXOSCALE_BUCKET,
                Key: doc.back_object_storage_id
              });
              backUrl = await getSignedUrl(s3, command, { expiresIn: 900 });
            } catch (err) { backUrl = null; }
          }
          return { ...doc, frontUrl, backUrl };
        })
      );

      const hasObjects = async (prefix) => {
        try {
          const res = await s3.send(new ListObjectsV2Command({
            Bucket: process.env.EXOSCALE_BUCKET,
            Prefix: prefix,
            MaxKeys: 1
          }));
          return !!(res && res.Contents && res.Contents.length);
        } catch (e) {
          logger.warn('AdminService.getDetailedUserInfo:storage_check_failed', { userId, prefix, error: e && e.message });
          return false;
        }
      };

      const userType = (user.user_type || '').toString().toLowerCase();
      const idPrefix = userType === 'company' ? `company-id/${userId}/` : `personal-id/${userId}/`;
      const contractBase = `contracts/${userType === 'company' ? 'company' : 'personal'}/${userId}/`;
      const [idDocumentsPresent, contractPresent, gdprPresent] = await Promise.all([
        hasObjects(idPrefix),
        hasObjects(`${contractBase}contract/`),
        hasObjects(`${contractBase}gdpr/`)
      ]);

      const storageStatus = {
        idDocumentsPresent,
        contractPresent: contractPresent || gdprPresent
      };

      logger.info('AdminService.getDetailedUserInfo:success', { userId });
      return {
        user,
        personalProfile,
        companyProfile,
        permissions,
        userStatus,
        documents,
        contracts,
        idDocuments: idDocumentsWithUrls,
        storageStatus
      };
    } catch (error) {
      logger.error('AdminService.getDetailedUserInfo:error', { userId, error: error.message });
      throw error;
    }
  }

  static async updateUserPermissions(unitOfWork, userId, permissions) {
    logger.info('AdminService.updateUserPermissions:start', { userId, permissions });
    try {
      await AdminRepository.updateUserPermissions(unitOfWork.connection, userId, permissions);
      logger.info('AdminService.updateUserPermissions:success', { userId, permissions });
    } catch (error) {
      logger.error('AdminService.updateUserPermissions:error', { userId, error: error.message });
      throw error;
    }
  }

  static async getServerStatus() {
    logger.info('AdminService.getServerStatus:start');
    try {
      const stats = await pidusage(process.pid);
      const uptimeSeconds = process.uptime();
      const uptimeDays = Math.floor(uptimeSeconds / (60 * 60 * 24));
      const uptimeHours = Math.floor((uptimeSeconds % (60 * 60 * 24)) / 3600);
      const totalMem = os.totalmem();

      logger.info('AdminService.getServerStatus:success', { status: 'Online', uptime: { days: uptimeDays, hours: uptimeHours } });
      return {
        status: 'Online',
        uptime: { days: uptimeDays, hours: uptimeHours },
        cpuUsagePercent: Math.round(stats.cpu),
        memory: {
          used: (stats.memory / (1024 ** 3)).toFixed(1),
          total: (totalMem / (1024 ** 3)).toFixed(1)
        },
        logs: []
      };
    } catch (error) {
      logger.error('AdminService.getServerStatus:error', { error: error.message });
      throw error;
    }
  }

  static async deleteUser(unitOfWork, userId) {
    logger.info('AdminService.deleteUser:start', { userId });
    try {
      // 1. Fetch all object storage IDs for user documents
      const docRepo = new UserDocumentRepository(unitOfWork);
      const objectIds = await docRepo.getAllObjectStorageIdsForUser(userId);

      // 2. Delete each document from S3
      for (const obj of objectIds) {
        try {
          if (obj.object_storage_id) {
            await s3.send(new DeleteObjectCommand({
              Bucket: process.env.EXOSCALE_BUCKET,
              Key: obj.object_storage_id
            }));
            logger.info('AdminService.deleteUser:s3_deleted', { userId, objectKey: obj.object_storage_id });
          }
          // For user_id_documents: front/back
          if (obj.front_object_storage_id) {
            await s3.send(new DeleteObjectCommand({
              Bucket: process.env.EXOSCALE_BUCKET,
              Key: obj.front_object_storage_id
            }));
            logger.info('AdminService.deleteUser:s3_deleted', { userId, objectKey: obj.front_object_storage_id });
          }
          if (obj.back_object_storage_id) {
            await s3.send(new DeleteObjectCommand({
              Bucket: process.env.EXOSCALE_BUCKET,
              Key: obj.back_object_storage_id
            }));
            logger.info('AdminService.deleteUser:s3_deleted', { userId, objectKey: obj.back_object_storage_id });
          }
        } catch (err) {
          logger.warn('AdminService.deleteUser:s3_delete_failed', { userId, error: err.message, objectKey: obj.object_storage_id || obj.front_object_storage_id || obj.back_object_storage_id });
        }
      }

      // 3. Delete user from DB (cascades)
      const userRepo = new UserRepository(unitOfWork);
      await userRepo.deleteUserById(userId);

      logger.info('AdminService.deleteUser:success', { userId });
    } catch (error) {
      logger.error('AdminService.deleteUser:error', { userId, error: error.message });
      throw error;
    }
  }

  static async archiveUser(unitOfWork, userId) {
    logger.info('AdminService.archiveUser:start', { userId });
    try {
      // First, get the current status to store it
      const [statusRows] = await unitOfWork.connection.query(
        `SELECT status FROM user_status WHERE user_id = ? LIMIT 1`,
        [userId]
      );
      
      if (statusRows.length === 0) {
        throw new Error('User status not found');
      }
      
      const currentStatus = statusRows[0].status;
      
      // Don't archive if already archived
      if (currentStatus === 'archived') {
        logger.warn('AdminService.archiveUser:already_archived', { userId });
        return { message: 'User is already archived' };
      }
      
      // Store the previous status and update to archived
      await unitOfWork.connection.query(
        `UPDATE user_status 
         SET status = 'archived', 
             previous_status = ? 
         WHERE user_id = ?`,
        [currentStatus, userId]
      );

      logger.info('AdminService.archiveUser:success', { userId, previousStatus: currentStatus });
    } catch (error) {
      logger.error('AdminService.archiveUser:error', { userId, error: error.message });
      throw error;
    }
  }

  static async unarchiveUser(unitOfWork, userId) {
    logger.info('AdminService.unarchiveUser:start', { userId });
    try {
      // Get the current status and previous status
      const [statusRows] = await unitOfWork.connection.query(
        `SELECT status, previous_status FROM user_status WHERE user_id = ? LIMIT 1`,
        [userId]
      );
      
      if (statusRows.length === 0) {
        throw new Error('User status not found');
      }
      
      const currentStatus = statusRows[0].status;
      const previousStatus = statusRows[0].previous_status;
      
      // Only unarchive if currently archived
      if (currentStatus !== 'archived') {
        logger.warn('AdminService.unarchiveUser:not_archived', { userId, currentStatus });
        return { message: 'User is not archived' };
      }
      
      // Restore to previous status or default to 'pending' if no previous status exists
      const restoreStatus = previousStatus || 'pending';
      
      await unitOfWork.connection.query(
        `UPDATE user_status 
         SET status = ?, 
             previous_status = NULL 
         WHERE user_id = ?`,
        [restoreStatus, userId]
      );

      logger.info('AdminService.unarchiveUser:success', { userId, restoredStatus: restoreStatus });
    } catch (error) {
      logger.error('AdminService.unarchiveUser:error', { userId, error: error.message });
      throw error;
    }
  }

  static async updateUserStatus(unitOfWork, userId, newStatus) {
    logger.info('AdminService.updateUserStatus:start', { userId, newStatus });
    try {
      // Validate status
      const validStatuses = ['inactive', 'pending', 'active', 'suspended', 'archived'];
      if (!validStatuses.includes(newStatus)) {
        throw new Error(`Invalid status: ${newStatus}. Must be one of: ${validStatuses.join(', ')}`);
      }

      // Get current status
      const [statusRows] = await unitOfWork.connection.query(
        `SELECT status FROM user_status WHERE user_id = ? LIMIT 1`,
        [userId]
      );
      
      if (statusRows.length === 0) {
        throw new Error('User status not found');
      }
      
      const currentStatus = statusRows[0].status;
      
      // Don't update if already at target status
      if (currentStatus === newStatus) {
        logger.warn('AdminService.updateUserStatus:already_at_status', { userId, status: newStatus });
        return { message: `User is already ${newStatus}` };
      }
      
      // Update status
      await unitOfWork.connection.query(
        `UPDATE user_status 
         SET status = ? 
         WHERE user_id = ?`,
        [newStatus, userId]
      );

      logger.info('AdminService.updateUserStatus:success', { userId, oldStatus: currentStatus, newStatus });
      return { message: `User status updated from ${currentStatus} to ${newStatus}` };
    } catch (error) {
      logger.error('AdminService.updateUserStatus:error', { userId, error: error.message });
      throw error;
    }
  }

  static async updateUserVerification(unitOfWork, userId, isAdminVerified) {
    logger.info('AdminService.updateUserVerification:start', { userId, isAdminVerified });
    try {
      // Update the is_admin_verified field in user_status
      const timestamp = isAdminVerified === 1 ? new Date() : null;
      
      await unitOfWork.connection.query(
        `UPDATE user_status 
         SET is_admin_verified = ?, 
             admin_verified_at = ?,
             status = CASE
               WHEN status IN ('suspended','archived') THEN status
               WHEN ? = 1 THEN 'active'
               ELSE 'pending'
             END
         WHERE user_id = ?`,
        [isAdminVerified, timestamp, isAdminVerified, userId]
      );

      logger.info('AdminService.updateUserVerification:success', { userId, isAdminVerified });
    } catch (error) {
      logger.error('AdminService.updateUserVerification:error', { userId, error: error.message });
      throw error;
    }
  }

  static async updateUserProfile(unitOfWork, userId, profileData, userType) {
    logger.info('AdminService.updateUserProfile:start', { userId, userType });
    try {
      if (userType === 'personal') {
        const { first_name, last_name, phone, date_of_birth, address, city, zip_code, country } = profileData;
        await unitOfWork.connection.query(
          `UPDATE personal_profiles 
           SET first_name = ?, last_name = ?, phone = ?, date_of_birth = ?, 
               address = ?, city = ?, zip_code = ?, country = ?
           WHERE user_id = ?`,
          [first_name, last_name, phone, date_of_birth, address, city, zip_code, country, userId]
        );
      } else if (userType === 'company') {
        const { company_name, registration_number, tax_id, phone, address, city, zip_code, country } = profileData;
        await unitOfWork.connection.query(
          `UPDATE company_profiles 
           SET company_name = ?, registration_number = ?, tax_id = ?, phone = ?, 
               address = ?, city = ?, zip_code = ?, country = ?
           WHERE user_id = ?`,
          [company_name, registration_number, tax_id, phone, address, city, zip_code, country, userId]
        );
      }

      logger.info('AdminService.updateUserProfile:success', { userId, userType });
    } catch (error) {
      logger.error('AdminService.updateUserProfile:error', { userId, error: error.message });
      throw error;
    }
  }
}

module.exports = AdminService;
