import {
  S3Client,
  HeadBucketCommand,
  CreateBucketCommand,
  PutObjectCommand,
  GetObjectCommand,
  DeleteObjectCommand,
  HeadObjectCommand,
  ListObjectsV2Command,
  GetObjectCommand as GetObjectCmd,
} from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import log4js from 'log4js';
import dotenv from 'dotenv';

dotenv.config();

const logger = log4js.getLogger('CephStorage');
logger.level = process.env.LOG_LEVEL;

// Configure AWS SDK v3 for Ceph (S3-compatible)
const s3Config = {
  credentials: {
    accessKeyId: process.env.CEPH_ACCESS_KEY_ID,
    secretAccessKey: process.env.CEPH_SECRET_ACCESS_KEY,
  },
  endpoint: process.env.CEPH_ENDPOINT, // e.g., 'http://ceph-gateway:7480'
  forcePathStyle: true, // Required for Ceph (renamed from s3ForcePathStyle)
  region: process.env.CEPH_REGION || 'us-east-1',
};

const s3Client = new S3Client(s3Config);

// Default bucket names for different file types
const BUCKETS = {
  FILES: process.env.CEPH_FILES_BUCKET || 'farmcontrol',
};

/**
 * Initialize buckets if they don't exist
 */
export const initializeBuckets = async () => {
  try {
    for (const [type, bucketName] of Object.entries(BUCKETS)) {
      try {
        await s3Client.send(new HeadBucketCommand({ Bucket: bucketName }));
        logger.debug(`Bucket ${bucketName} already exists`);
      } catch (error) {
        if (error.name === 'NotFound' || error.$metadata?.httpStatusCode === 404) {
          await s3Client.send(new CreateBucketCommand({ Bucket: bucketName }));
          logger.info(`Created bucket: ${bucketName}`);
        } else {
          logger.error(`Error checking bucket ${bucketName}:`, error);
        }
      }
    }
  } catch (error) {
    logger.error('Error initializing buckets:', error);
    throw error;
  }
};

/**
 * Upload a file to Ceph
 * @param {string} bucket - Bucket name
 * @param {string} key - Object key (file path)
 * @param {Buffer} body - File content
 * @param {string} contentType - MIME type
 * @param {Object} metadata - Additional metadata
 * @returns {Promise<Object>} Upload result
 */
export const uploadFile = async (bucket, key, body, contentType, metadata = {}) => {
  try {
    const params = {
      Bucket: bucket,
      Key: key,
      Body: body,
      ContentType: contentType,
      Metadata: metadata,
    };

    await s3Client.send(new PutObjectCommand(params));
    const result = { Location: `${process.env.CEPH_ENDPOINT}/${bucket}/${key}` };
    logger.debug(`File uploaded successfully: ${key} to bucket ${bucket}`);
    return result;
  } catch (error) {
    logger.error(`Error uploading file ${key} to bucket ${bucket}:`, error);
    throw error;
  }
};

/**
 * Download a file from Ceph
 * @param {string} bucket - Bucket name
 * @param {string} key - Object key (file path)
 * @returns {Promise<Buffer>} File content
 */
export const downloadFile = async (bucket, key) => {
  try {
    const params = {
      Bucket: bucket,
      Key: key,
    };

    const result = await s3Client.send(new GetObjectCommand(params));
    logger.debug(`File downloaded successfully: ${key} from bucket ${bucket}`);
    return result.Body;
  } catch (error) {
    if (error.name === 'NotFound' || error.$metadata?.httpStatusCode === 404) {
      logger.warn(`File not found: ${key} in bucket ${bucket}`);
      throw new Error('File not found');
    }
    logger.error(`Error downloading file ${key} from bucket ${bucket}:`, error);
    throw error;
  }
};

/**
 * Delete a file from Ceph
 * @param {string} bucket - Bucket name
 * @param {string} key - Object key (file path)
 * @returns {Promise<Object>} Delete result
 */
export const deleteFile = async (bucket, key) => {
  try {
    const params = {
      Bucket: bucket,
      Key: key,
    };

    const result = await s3Client.send(new DeleteObjectCommand(params));
    logger.debug(`File deleted successfully: ${key} from bucket ${bucket}`);
    return result;
  } catch (error) {
    logger.error(`Error deleting file ${key} from bucket ${bucket}:`, error);
    throw error;
  }
};

/**
 * Check if a file exists in Ceph
 * @param {string} bucket - Bucket name
 * @param {string} key - Object key (file path)
 * @returns {Promise<boolean>} True if file exists
 */
export const fileExists = async (bucket, key) => {
  try {
    await s3Client.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));
    return true;
  } catch (error) {
    if (error.name === 'NotFound' || error.$metadata?.httpStatusCode === 404) {
      return false;
    }
    logger.error(`Error checking file existence ${key} in bucket ${bucket}:`, error);
    throw error;
  }
};

/**
 * List files in a bucket with optional prefix
 * @param {string} bucket - Bucket name
 * @param {string} prefix - Optional prefix to filter files
 * @returns {Promise<Array>} List of file objects
 */
export const listFiles = async (bucket, prefix = '') => {
  try {
    const params = {
      Bucket: bucket,
      Prefix: prefix,
    };

    const result = await s3Client.send(new ListObjectsV2Command(params));
    logger.debug(`Listed ${result.Contents.length} files in bucket ${bucket}`);
    return result.Contents;
  } catch (error) {
    logger.error(`Error listing files in bucket ${bucket}:`, error);
    throw error;
  }
};

/**
 * Get file metadata from Ceph
 * @param {string} bucket - Bucket name
 * @param {string} key - Object key (file path)
 * @returns {Promise<Object>} File metadata
 */
export const getFileMetadata = async (bucket, key) => {
  try {
    const params = {
      Bucket: bucket,
      Key: key,
    };

    const result = await s3Client.send(new HeadObjectCommand(params));
    logger.debug(`Retrieved metadata for file: ${key} in bucket ${bucket}`);
    return result;
  } catch (error) {
    if (error.name === 'NotFound' || error.$metadata?.httpStatusCode === 404) {
      logger.warn(`File not found: ${key} in bucket ${bucket}`);
      throw new Error('File not found');
    }
    logger.error(`Error getting metadata for file ${key} in bucket ${bucket}:`, error);
    throw error;
  }
};

/**
 * Generate a presigned URL for file access
 * @param {string} bucket - Bucket name
 * @param {string} key - Object key (file path)
 * @param {number} expiresIn - URL expiration time in seconds (default: 3600)
 * @returns {Promise<string>} Presigned URL
 */
export const getPresignedUrl = async (bucket, key, expiresIn = 3600) => {
  try {
    const params = {
      Bucket: bucket,
      Key: key,
    };

    const url = await getSignedUrl(s3Client, new GetObjectCommand(params), { expiresIn });
    logger.debug(`Generated presigned URL for file: ${key} in bucket ${bucket}`);
    return url;
  } catch (error) {
    logger.error(`Error generating presigned URL for file ${key} in bucket ${bucket}:`, error);
    throw error;
  }
};

// Export bucket constants for use in other modules
export { BUCKETS };
