import { CallHandler, ExecutionContext, Injectable, NestInterceptor, Inject } from '@nestjs/common';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import * as zlib from 'zlib';
import { promisify } from 'util';
import { HttpAdapterHost } from '@nestjs/core';

// Promisify các hàm nén/giải nén
const gzip = promisify(zlib.gzip);
const gunzip = promisify(zlib.gunzip);

// Ngưỡng nén: Chỉ nén nếu chuỗi JSON lớn hơn ngưỡng này (bytes)
// Nén dữ liệu quá nhỏ có thể làm tăng kích thước do overhead của header nén
const COMPRESSION_THRESHOLD = 100; 

@Injectable()
export class CompressCacheInterceptor implements NestInterceptor {
  constructor(
    @Inject(CACHE_MANAGER) private cacheManager: Cache,
    private readonly httpAdapterHost: HttpAdapterHost, // Để truy cập request/response
  ) {}

  async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
    const httpAdapter = this.httpAdapterHost.httpAdapter;
    const request = context.getArgByIndex(0);
    const response = context.getArgByIndex(1);

    // Chỉ áp dụng cho các request GET
    if (httpAdapter.getRequestMethod(request) !== 'GET') {
      return next.handle();
    }

    const cacheKey = httpAdapter.getRequestUrl(request);
    let cachedData = await this.cacheManager.get<Buffer>(cacheKey);

    if (cachedData) {
      try {
        // Kiểm tra xem dữ liệu có phải là Buffer và có Gzip header (0x1f 0x8b) không
        if (Buffer.isBuffer(cachedData) && cachedData.length > 2 && cachedData[0] === 0x1f && cachedData[1] === 0x8b) {
          const decompressed = await gunzip(cachedData);
          const jsonString = decompressed.toString('utf8');
          
          httpAdapter.setHeader(response, 'Content-Type', 'application/json');
          httpAdapter.setHeader(response, 'X-Cache', 'HIT (Compressed)');
          return of(JSON.parse(jsonString));
        } else {
          // Dữ liệu không nén (lưu dưới dạng string hoặc buffer thường)
          const jsonString = cachedData.toString();
          httpAdapter.setHeader(response, 'Content-Type', 'application/json');
          httpAdapter.setHeader(response, 'X-Cache', 'HIT (Uncompressed)');
          return of(JSON.parse(jsonString));
        }
      } catch (e) {
        console.error(`[Cache] Lỗi giải nén dữ liệu cache cho key ${cacheKey}:`, e);
        // Nếu giải nén lỗi, coi như cache miss và xóa cache bị lỗi
        await this.cacheManager.del(cacheKey); 
      }
    }

    // Cache miss hoặc giải nén lỗi, tiếp tục xử lý request
    // Đặt header MISS ngay lập tức trước khi chạy logic Controller
    httpAdapter.setHeader(response, 'X-Cache', 'MISS');

    return next.handle().pipe(
      tap(async (data) => { // Sử dụng tap để thực hiện side effect (lưu cache) mà không thay đổi dữ liệu gốc
        if (!data) return;

        const jsonString = JSON.stringify(data);
        const ttl = 60000; // TTL mặc định 1 phút (có thể cấu hình từ CACHE_TTL.DEFAULT)

        if (jsonString.length > COMPRESSION_THRESHOLD) {
          try {
            const compressed = await gzip(Buffer.from(jsonString, 'utf8'));
            await this.cacheManager.set(cacheKey, compressed, ttl);
            console.log(`[Cache] 📦 Đã nén dữ liệu cho: ${cacheKey} (${jsonString.length} -> ${compressed.length} bytes)`);
            // Không setHeader ở đây vì response có thể đã gửi xong
          } catch (e) {
            console.error(`[Cache] Lỗi nén dữ liệu cho key ${cacheKey}:`, e);
            // Nếu nén lỗi, lưu dữ liệu không nén làm fallback
            await this.cacheManager.set(cacheKey, jsonString, ttl); 
          }
        } else {
          // Dữ liệu quá nhỏ, lưu không nén
          await this.cacheManager.set(cacheKey, jsonString, ttl);
        }
      })
    );
  }
}