const {
  S3Client,
  PutObjectCommand,
  GetObjectCommand,
  HeadObjectCommand,
} = require("@aws-sdk/client-s3");
const { getSignedUrl } = require("@aws-sdk/s3-request-presigner");
const { getAWSConfig } = require("../config/aws");
const { v4: uuidv4 } = require("uuid");
const path = require("path");
const logger = require("../utils/logger");

// Cache-Control: 24 hours for public content (allows moderation takedowns to propagate)
// Private content (messages, condition-checks) uses presigned URLs so cache doesn't matter as much
const DEFAULT_CACHE_MAX_AGE = 86400; // 24 hours in seconds

const UPLOAD_CONFIGS = {
  profile: {
    folder: "profiles",
    maxSize: 5 * 1024 * 1024,
    cacheMaxAge: DEFAULT_CACHE_MAX_AGE,
    public: true,
  },
  item: {
    folder: "items",
    maxSize: 10 * 1024 * 1024,
    cacheMaxAge: DEFAULT_CACHE_MAX_AGE,
    public: true,
  },
  message: {
    folder: "messages",
    maxSize: 5 * 1024 * 1024,
    cacheMaxAge: 3600,
    public: false,
  },
  forum: {
    folder: "forum",
    maxSize: 10 * 1024 * 1024,
    cacheMaxAge: DEFAULT_CACHE_MAX_AGE,
    public: true,
  },
  "condition-check": {
    folder: "condition-checks",
    maxSize: 10 * 1024 * 1024,
    cacheMaxAge: 3600,
    public: false,
  },
};

const ALLOWED_TYPES = [
  "image/jpeg",
  "image/jpg",
  "image/png",
  "image/gif",
  "image/webp",
];
const PRESIGN_EXPIRY = 300; // 5 minutes

class S3Service {
  constructor() {
    this.client = null;
    this.bucket = null;
    this.region = null;
    this.enabled = false;
  }

  /**
   * Check if S3 is enabled
   * @returns {boolean}
   */
  isEnabled() {
    return this.enabled;
  }

  initialize() {
    if (process.env.S3_ENABLED !== "true") {
      logger.info("S3 Service disabled (S3_ENABLED !== true)");
      this.enabled = false;
      return;
    }

    // S3 is enabled - validate required configuration
    const bucket = process.env.S3_BUCKET;
    if (!bucket) {
      logger.error("S3_ENABLED=true but S3_BUCKET is not set");
      process.exit(1);
    }

    try {
      const config = getAWSConfig();
      this.client = new S3Client({
        ...config,
        // Disable automatic checksums - browser uploads can't calculate them
        requestChecksumCalculation: "WHEN_REQUIRED",
      });
      this.bucket = bucket;
      this.region = config.region || "us-east-1";
      this.enabled = true;
      logger.info("S3 Service initialized", {
        bucket: this.bucket,
        region: this.region,
      });
    } catch (error) {
      logger.error("Failed to initialize S3 Service", { error: error.message, stack: error.stack });
      process.exit(1);
    }
  }

  /**
   * Check if image processing (metadata stripping) is enabled
   * When enabled, uploads go to staging/ prefix and Lambda processes them
   * @returns {boolean}
   */
  isImageProcessingEnabled() {
    return process.env.IMAGE_PROCESSING_ENABLED === "true";
  }

  /**
   * Get a presigned URL for uploading a file directly to S3
   * @param {string} uploadType - Type of upload (profile, item, message, forum, condition-check)
   * @param {string} contentType - MIME type of the file
   * @param {string} fileName - Original filename (used for extension)
   * @param {number} fileSize - File size in bytes (required for size enforcement)
   * @param {string} [baseKey] - Optional base key (UUID) for coordinated variant uploads
   * @returns {Promise<{uploadUrl: string, key: string, stagingKey: string|null, publicUrl: string, expiresAt: Date}>}
   */
  async getPresignedUploadUrl(uploadType, contentType, fileName, fileSize, baseKey = null) {
    if (!this.enabled) {
      throw new Error("S3 storage is not enabled");
    }

    const config = UPLOAD_CONFIGS[uploadType];
    if (!config) {
      throw new Error(`Invalid upload type: ${uploadType}`);
    }
    if (!ALLOWED_TYPES.includes(contentType)) {
      throw new Error(`Invalid content type: ${contentType}`);
    }
    if (!fileSize || fileSize <= 0) {
      throw new Error("File size is required");
    }
    if (fileSize > config.maxSize) {
      throw new Error(
        `File too large. Maximum size is ${config.maxSize / (1024 * 1024)}MB`
      );
    }

    // Extract known variant suffix from fileName if present (e.g., "photo_th.jpg" -> "_th")
    const ext = path.extname(fileName) || this.getExtFromMime(contentType);
    const baseName = path.basename(fileName, ext);

    // Only recognize known variant suffixes
    let suffix = "";
    if (baseName.endsWith("_th")) {
      suffix = "_th";
    } else if (baseName.endsWith("_md")) {
      suffix = "_md";
    }

    // Use provided baseKey or generate new UUID
    const uuid = baseKey || uuidv4();

    // Final key is where the processed image will be (what frontend stores in DB)
    const finalKey = `${config.folder}/${uuid}${suffix}${ext}`;

    // When image processing is enabled, upload to staging/ prefix
    // Lambda will process and move to final location
    const useStaging = this.isImageProcessingEnabled();
    const uploadKey = useStaging ? `staging/${finalKey}` : finalKey;

    const cacheDirective = config.public ? "public" : "private";
    const command = new PutObjectCommand({
      Bucket: this.bucket,
      Key: uploadKey,
      ContentType: contentType,
      ContentLength: fileSize, // Enforce exact file size
      CacheControl: `${cacheDirective}, max-age=${config.cacheMaxAge}`,
    });

    const uploadUrl = await getSignedUrl(this.client, command, {
      expiresIn: PRESIGN_EXPIRY,
    });

    return {
      uploadUrl,
      key: finalKey, // Frontend stores this in database
      stagingKey: useStaging ? uploadKey : null, // Actual upload location (if staging enabled)
      publicUrl: config.public
        ? `https://${this.bucket}.s3.${this.region}.amazonaws.com/${finalKey}`
        : null,
      expiresAt: new Date(Date.now() + PRESIGN_EXPIRY * 1000),
    };
  }

  /**
   * Get a presigned URL for downloading a private file from S3
   * @param {string} key - S3 object key
   * @param {number} expiresIn - Expiration time in seconds (default 1 hour)
   * @returns {Promise<string>}
   */
  async getPresignedDownloadUrl(key, expiresIn = 3600) {
    if (!this.enabled) {
      throw new Error("S3 storage is not enabled");
    }

    const command = new GetObjectCommand({
      Bucket: this.bucket,
      Key: key,
    });
    return getSignedUrl(this.client, command, { expiresIn });
  }

  /**
   * Get the public URL for a file (only for public folders)
   * @param {string} key - S3 object key
   * @returns {string|null}
   */
  getPublicUrl(key) {
    if (!this.enabled) {
      return null;
    }
    return `https://${this.bucket}.s3.${this.region}.amazonaws.com/${key}`;
  }

  /**
   * Verify that a file exists in S3
   * @param {string} key - S3 object key
   * @returns {Promise<boolean>}
   */
  async verifyUpload(key) {
    if (!this.enabled) {
      return false;
    }

    try {
      await this.client.send(
        new HeadObjectCommand({
          Bucket: this.bucket,
          Key: key,
        })
      );
      return true;
    } catch (err) {
      if (err.name === "NotFound" || err.$metadata?.httpStatusCode === 404) {
        return false;
      }
      throw err;
    }
  }

  /**
   * Get file extension from MIME type
   * @param {string} mime - MIME type
   * @returns {string}
   */
  getExtFromMime(mime) {
    const map = {
      "image/jpeg": ".jpg",
      "image/jpg": ".jpg",
      "image/png": ".png",
      "image/gif": ".gif",
      "image/webp": ".webp",
    };
    return map[mime] || ".jpg";
  }
}

const s3Service = new S3Service();
module.exports = s3Service;
