import { Injectable, Logger } from "@nestjs/common"
import { ConfigService } from "@nestjs/config"
import {
  S3Client,
  PutObjectCommand,
  DeleteObjectCommand,
} from "@aws-sdk/client-s3"
import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
import { Readable } from "stream"

@Injectable()
export class CloudflareR2Service {
  private readonly logger = new Logger(CloudflareR2Service.name)
  private readonly s3Client: S3Client
  private readonly bucketName: string
  private readonly publicUrl: string

  constructor(private readonly configService: ConfigService) {
    // Access config from the CLOUDFLARE_R2 namespace
    const r2Config = this.configService.get("CLOUDFLARE_R2")
    const accountId =
      r2Config?.accountId || process.env.CLOUDFLARE_R2_ACCOUNT_ID
    const accessKeyId =
      r2Config?.accessKeyId || process.env.CLOUDFLARE_R2_ACCESS_KEY_ID
    const secretAccessKey =
      r2Config?.secretAccessKey || process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY
    const bucket = r2Config?.bucketName || process.env.CLOUDFLARE_R2_BUCKET_NAME
    const publicUrlBase =
      r2Config?.publicUrl || process.env.CLOUDFLARE_R2_PUBLIC_URL

    if (!accountId || !accessKeyId || !secretAccessKey || !bucket) {
      this.logger.warn(
        "Cloudflare R2 credentials not fully configured. File uploads may fail.",
      )
    }

    this.bucketName = bucket || ""
    this.publicUrl = publicUrlBase || ""

    // Cloudflare R2 uses S3-compatible API
    // Important: forcePathStyle is required for R2 presigned URLs to work correctly
    this.s3Client = new S3Client({
      region: "auto",
      endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
      credentials: {
        accessKeyId: accessKeyId || "",
        secretAccessKey: secretAccessKey || "",
      },
      forcePathStyle: true, // Required for R2 compatibility - uses path-style URLs
    })
  }

  /**
   * Sanitize S3 key - replace spaces with hyphens and special chars with underscores
   * @param key - The original key
   * @returns Sanitized key safe for R2/S3
   */
  private sanitizeS3Key(key: string): string {
    return key
      .split("/")
      .map(
        (segment) =>
          segment
            .replace(/\s+/g, "-") // Replace spaces with hyphens
            .replace(/[^a-zA-Z0-9-_.]/g, "_"), // Replace other special characters with underscores
      )
      .join("/")
  }

  /**
   * Upload a file to Cloudflare R2
   * @param file - The file buffer or stream
   * @param key - The object key (path) in the bucket
   * @param contentType - The MIME type of the file
   * @returns The public URL of the uploaded file
   */
  async uploadFile(
    file: Buffer | Readable,
    key: string,
    contentType: string,
  ): Promise<string> {
    try {
      // Sanitize the key before uploading
      const sanitizedKey = this.sanitizeS3Key(key)

      const command = new PutObjectCommand({
        Bucket: this.bucketName,
        Key: sanitizedKey,
        Body: file,
        ContentType: contentType,
      })

      await this.s3Client.send(command)

      // Construct the public URL with sanitized key
      const fileUrl = `${this.publicUrl}/${sanitizedKey}`
      this.logger.log(`File uploaded successfully: ${fileUrl}`)

      return fileUrl
    } catch (error) {
      this.logger.error(
        `Failed to upload file to R2: ${error.message}`,
        error.stack,
      )
      throw new Error(
        `Failed to upload file to Cloudflare R2: ${error.message}`,
      )
    }
  }

  /**
   * Delete a file from Cloudflare R2
   * @param key - The object key (path) in the bucket
   */
  async deleteFile(key: string): Promise<void> {
    try {
      // Sanitize the key before deleting (in case it was stored with original name)
      const sanitizedKey = this.sanitizeS3Key(key)

      const command = new DeleteObjectCommand({
        Bucket: this.bucketName,
        Key: sanitizedKey,
      })

      await this.s3Client.send(command)
      this.logger.log(`File deleted successfully: ${sanitizedKey}`)
    } catch (error) {
      this.logger.error(
        `Failed to delete file from R2: ${error.message}`,
        error.stack,
      )
      throw new Error(
        `Failed to delete file from Cloudflare R2: ${error.message}`,
      )
    }
  }

  /**
   * Generate a presigned URL for uploading a file
   * @param key - The object key (path) in the bucket
   * @param contentType - The MIME type of the file (default: application/pdf)
   * @param expiresIn - URL expiration time in seconds (default: 900 = 15 minutes)
   * @returns Presigned URL for uploading
   */
  async generatePresignedUploadUrl(
    key: string,
    contentType: string = "application/pdf",
    expiresIn: number = 900,
  ): Promise<string> {
    try {
      // Sanitize the key before generating presigned URL
      const sanitizedKey = this.sanitizeS3Key(key)

      // Create command for presigned URL
      // Note: Do not include Body or checksum parameters in presigned URL command
      const command = new PutObjectCommand({
        Bucket: this.bucketName,
        Key: sanitizedKey,
        ContentType: contentType,
      })

      const presignedUrl = await getSignedUrl(this.s3Client, command, {
        expiresIn,
      })

      // Log the generated URL (without sensitive query params)
      const urlObj = new URL(presignedUrl)
      this.logger.log(
        `Presigned upload URL generated for key: ${sanitizedKey} (expires in ${expiresIn}s)`,
      )
      this.logger.debug(`Presigned URL host: ${urlObj.host}`)

      return presignedUrl
    } catch (error) {
      this.logger.error(
        `Failed to generate presigned URL: ${error.message}`,
        error.stack,
      )
      throw new Error(
        `Failed to generate presigned upload URL: ${error.message}`,
      )
    }
  }

  /**
   * Validate if a URL belongs to our R2 bucket
   * @param url - The URL to validate
   * @returns True if URL is valid and belongs to our bucket
   */
  validateR2Url(url: string): boolean {
    if (!url || !this.publicUrl) return false

    try {
      const urlObj = new URL(url)
      const publicUrlObj = new URL(this.publicUrl)

      // Check if the URL domain matches our public URL domain
      return urlObj.origin === publicUrlObj.origin
    } catch {
      return false
    }
  }

  /**
   * Extract the key from a full R2 URL
   * @param url - The full public URL
   * @returns The object key (sanitized)
   */
  extractKeyFromUrl(url: string): string {
    if (!url) return ""
    try {
      const urlObj = new URL(url)
      // Remove leading slash from pathname
      const key = urlObj.pathname.startsWith("/")
        ? urlObj.pathname.substring(1)
        : urlObj.pathname

      // Decode URL encoding (e.g., %20 -> space) then sanitize
      const decodedKey = decodeURIComponent(key)
      return this.sanitizeS3Key(decodedKey)
    } catch {
      // If URL parsing fails, assume it's already a key
      const key = url.startsWith("/") ? url.substring(1) : url
      return this.sanitizeS3Key(key)
    }
  }
}
