import { CreateBucketCommand, GetObjectCommand, HeadBucketCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3'
import { Readable } from 'node:stream'

export type ObjectBody = { stream: Readable; contentLength?: number }

export interface ObjectStorage {
  readonly bucket: string
  readonly provider: 's3' | 'memory'
  initialize(): Promise<void>
  putObject(key: string, body: Buffer, contentType: string): Promise<{ etag?: string }>
  getObject(key: string): Promise<ObjectBody | null>
}

export class MemoryObjectStorage implements ObjectStorage {
  readonly bucket: string
  readonly provider = 'memory' as const
  private readonly objects = new Map<string, Buffer>()

  constructor(bucket = 'osint-test-assets') { this.bucket = bucket }
  async initialize() { /* Nothing to initialize. */ }
  async putObject(key: string, body: Buffer) { this.objects.set(key, Buffer.from(body)); return {} }
  async getObject(key: string) {
    const body = this.objects.get(key)
    return body ? { stream: Readable.from(body), contentLength: body.byteLength } : null
  }
}

export class S3ObjectStorage implements ObjectStorage {
  readonly provider = 's3' as const
  readonly bucket: string
  private readonly client: S3Client

  constructor(options: { endpoint: string; region: string; accessKey: string; secretKey: string; bucket: string; forcePathStyle: boolean }) {
    this.bucket = options.bucket
    this.client = new S3Client({
      endpoint: options.endpoint,
      region: options.region,
      forcePathStyle: options.forcePathStyle,
      credentials: { accessKeyId: options.accessKey, secretAccessKey: options.secretKey },
    })
  }

  async initialize() {
    try {
      await this.client.send(new HeadBucketCommand({ Bucket: this.bucket }))
    } catch (error) {
      const status = (error as { $metadata?: { httpStatusCode?: number } }).$metadata?.httpStatusCode
      if (status !== 404) throw error
      try { await this.client.send(new CreateBucketCommand({ Bucket: this.bucket })) }
      catch (createError) {
        const createStatus = (createError as { $metadata?: { httpStatusCode?: number } }).$metadata?.httpStatusCode
        if (createStatus !== 409) throw createError
      }
    }
  }

  async putObject(key: string, body: Buffer, contentType: string) {
    const result = await this.client.send(new PutObjectCommand({ Bucket: this.bucket, Key: key, Body: body, ContentType: contentType }))
    return { etag: result.ETag?.replaceAll('"', '') }
  }

  async getObject(key: string) {
    try {
      const result = await this.client.send(new GetObjectCommand({ Bucket: this.bucket, Key: key }))
      if (!result.Body || typeof (result.Body as NodeJS.ReadableStream).pipe !== 'function') throw new Error(`Object ${key} did not return a Node stream`)
      return { stream: result.Body as Readable, contentLength: result.ContentLength }
    } catch (error) {
      const status = (error as { $metadata?: { httpStatusCode?: number } }).$metadata?.httpStatusCode
      if (status === 404) return null
      throw error
    }
  }
}

export function createObjectStorageFromEnv() {
  if (process.env.ASSET_STORAGE_DRIVER === 'memory' || process.env.NODE_ENV === 'test') return new MemoryObjectStorage(process.env.S3_BUCKET)
  const accessKey = process.env.S3_ACCESS_KEY
  const secretKey = process.env.S3_SECRET_KEY
  if (!accessKey || !secretKey) throw new Error('S3_ACCESS_KEY and S3_SECRET_KEY are required for MinIO asset storage')
  return new S3ObjectStorage({
    endpoint: process.env.S3_ENDPOINT || 'http://127.0.0.1:9000',
    region: process.env.S3_REGION || 'us-east-1',
    accessKey,
    secretKey,
    bucket: process.env.S3_BUCKET || 'osint-evidence',
    forcePathStyle: process.env.S3_FORCE_PATH_STYLE !== 'false',
  })
}
