import { createHmac } from 'crypto'
import * as fs from 'fs/promises'
import * as path from 'path'
import type { StorageProvider } from './types'

const SECRET_KEY = process.env.NEXTAUTH_SECRET || 'local-storage-secret'
const DEFAULT_BASE_PATH = './uploads'

/**
 * Local Filesystem Storage Provider
 *
 * Stores files in the local filesystem with signed URLs for access control.
 * Suitable for development/testing or deployments without S3.
 */
export class LocalStorageProvider implements StorageProvider {
  private basePath: string
  private baseUrl: string

  constructor(basePath?: string) {
    this.basePath = basePath || process.env.LOCAL_STORAGE_PATH || DEFAULT_BASE_PATH
    this.baseUrl = process.env.NEXTAUTH_URL || 'http://localhost:3000'
  }

  /**
   * Generate a signed URL for secure file access
   */
  private generateSignedUrl(
    key: string,
    action: 'upload' | 'download',
    expirySeconds: number
  ): string {
    const expiresAt = Math.floor(Date.now() / 1000) + expirySeconds
    const payload = `${action}:${key}:${expiresAt}`
    const signature = createHmac('sha256', SECRET_KEY)
      .update(payload)
      .digest('hex')

    const params = new URLSearchParams({
      key,
      action,
      expires: expiresAt.toString(),
      sig: signature,
    })

    return `${this.baseUrl}/api/storage/local?${params.toString()}`
  }

  /**
   * Verify a signed URL signature
   */
  static verifySignature(
    key: string,
    action: string,
    expiresAt: number,
    signature: string
  ): boolean {
    const payload = `${action}:${key}:${expiresAt}`
    const expectedSignature = createHmac('sha256', SECRET_KEY)
      .update(payload)
      .digest('hex')

    return signature === expectedSignature && expiresAt > Date.now() / 1000
  }

  private getFilePath(key: string): string {
    // Sanitize key to prevent path traversal
    const sanitizedKey = key.replace(/\.\./g, '').replace(/^\//, '')
    return path.join(this.basePath, sanitizedKey)
  }

  private async ensureDirectory(filePath: string): Promise<void> {
    const dir = path.dirname(filePath)
    await fs.mkdir(dir, { recursive: true })
  }

  async getUploadUrl(
    key: string,
    _contentType: string,
    expirySeconds: number = 900
  ): Promise<string> {
    return this.generateSignedUrl(key, 'upload', expirySeconds)
  }

  async getDownloadUrl(key: string, expirySeconds: number = 900): Promise<string> {
    return this.generateSignedUrl(key, 'download', expirySeconds)
  }

  async deleteObject(key: string): Promise<void> {
    const filePath = this.getFilePath(key)
    try {
      await fs.unlink(filePath)
    } catch (error) {
      // Ignore if file doesn't exist
      if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
        throw error
      }
    }
  }

  async putObject(key: string, data: Buffer, _contentType: string): Promise<void> {
    const filePath = this.getFilePath(key)
    await this.ensureDirectory(filePath)
    await fs.writeFile(filePath, data)
  }

  async getObject(key: string): Promise<Buffer> {
    const filePath = this.getFilePath(key)
    return fs.readFile(filePath)
  }

  async objectExists(key: string): Promise<boolean> {
    const filePath = this.getFilePath(key)
    try {
      await fs.access(filePath)
      return true
    } catch {
      return false
    }
  }

  /**
   * Get the file path for direct file serving (used by API route)
   */
  getAbsoluteFilePath(key: string): string {
    return path.resolve(this.getFilePath(key))
  }
}
