import {
  S3Client,
  PutObjectCommand,
  GetObjectCommand
} from "@aws-sdk/client-s3";
import { Storage } from "@google-cloud/storage";
import { logger } from "../monitoring/logger.js";
function getEnv(key, defaultValue) {
  const value = process.env[key];
  if (value === void 0) {
    if (defaultValue !== void 0) return defaultValue;
    throw new Error(`Environment variable ${key} is required`);
  }
  return value;
}
function getOptionalEnv(key) {
  return process.env[key];
}
var StorageTier = /* @__PURE__ */ ((StorageTier2) => {
  StorageTier2["HOT"] = "hot";
  StorageTier2["NEARLINE"] = "nearline";
  StorageTier2["COLDLINE"] = "coldline";
  StorageTier2["ARCHIVE"] = "archive";
  return StorageTier2;
})(StorageTier || {});
const DEFAULT_REMOTE_CONFIG = {
  provider: "gcs",
  // Default to GCS for better pricing
  gcs: {
    bucketName: "stackmemory-traces",
    projectId: process.env["GCP_PROJECT_ID"] || "stackmemory"
  },
  timeseries: {
    type: "sqlite",
    // Use SQLite for development
    host: "localhost",
    port: 0,
    database: "stackmemory_timeseries"
  },
  migration: {
    batchSize: 100,
    hotAgeHours: 168,
    // 7 days
    nearlineAgeHours: 720,
    // 30 days
    coldlineAgeHours: 2160,
    // 90 days
    archiveAgeHours: 8760,
    // 365 days
    scoreThreshold: 0.4
  }
};
class RemoteStorageManager {
  storageClient;
  config;
  localDb;
  migrationInProgress = false;
  constructor(localDb, config) {
    this.localDb = localDb;
    this.config = { ...DEFAULT_REMOTE_CONFIG, ...config };
    this.initializeStorageClient();
    this.initializeSchema();
  }
  /**
   * Initialize storage client based on provider
   */
  initializeStorageClient() {
    switch (this.config.provider) {
      case "gcs":
        if (this.config.gcs) {
          this.storageClient = new Storage({
            projectId: this.config.gcs.projectId,
            keyFilename: this.config.gcs.keyFilename
          });
        }
        break;
      case "s3":
      case "railway":
        if (this.config.s3?.accessKeyId && this.config.s3?.secretAccessKey) {
          this.storageClient = new S3Client({
            region: this.config.s3.region,
            credentials: {
              accessKeyId: this.config.s3.accessKeyId,
              secretAccessKey: this.config.s3.secretAccessKey
            },
            endpoint: this.config.s3.endpoint
            // Railway buckets endpoint
          });
        }
        break;
    }
  }
  /**
   * Initialize migration tracking schema
   */
  initializeSchema() {
    this.localDb.exec(`
      CREATE TABLE IF NOT EXISTS remote_migrations (
        trace_id TEXT PRIMARY KEY,
        migrated_at INTEGER NOT NULL,
        storage_tier TEXT NOT NULL,
        s3_key TEXT,
        timeseries_id TEXT,
        compression_level TEXT,
        original_size INTEGER,
        compressed_size INTEGER,
        retrieval_count INTEGER DEFAULT 0,
        last_retrieved INTEGER,
        FOREIGN KEY (trace_id) REFERENCES traces(id) ON DELETE CASCADE
      )
    `);
    this.localDb.exec(`
      CREATE INDEX IF NOT EXISTS idx_migrations_tier ON remote_migrations(storage_tier);
      CREATE INDEX IF NOT EXISTS idx_migrations_migrated ON remote_migrations(migrated_at);
    `);
  }
  /**
   * Identify traces for migration based on age and importance
   */
  async identifyMigrationCandidates() {
    const now = Date.now();
    const traces = this.localDb.prepare(
      `
      SELECT 
        t.id,
        t.score,
        t.start_time,
        LENGTH(t.compressed_data) + 
        COALESCE((SELECT SUM(LENGTH(tc.arguments) + LENGTH(tc.result)) 
                  FROM tool_calls tc WHERE tc.trace_id = t.id), 0) as size,
        rm.trace_id as already_migrated
      FROM traces t
      LEFT JOIN remote_migrations rm ON t.id = rm.trace_id
      WHERE rm.trace_id IS NULL  -- Not already migrated
      ORDER BY t.start_time ASC
    `
    ).all();
    const candidates = [];
    for (const trace of traces) {
      const ageHours = (now - trace.start_time) / (1e3 * 60 * 60);
      const candidate = this.evaluateTrace(
        trace.id,
        ageHours,
        trace.score,
        trace.size || 0
      );
      candidates.push(candidate);
    }
    return candidates;
  }
  /**
   * Evaluate a trace for migration based on GCS storage classes
   */
  evaluateTrace(traceId, ageHours, score, size) {
    let tier = "hot" /* HOT */;
    let shouldMigrate = false;
    let compressionLevel = "none";
    if (ageHours > this.config.migration.archiveAgeHours) {
      tier = "archive" /* ARCHIVE */;
      shouldMigrate = true;
      compressionLevel = "heavy";
    } else if (ageHours > this.config.migration.coldlineAgeHours) {
      tier = "coldline" /* COLDLINE */;
      shouldMigrate = true;
      compressionLevel = "heavy";
    } else if (ageHours > this.config.migration.nearlineAgeHours) {
      tier = "nearline" /* NEARLINE */;
      shouldMigrate = true;
      compressionLevel = "medium";
    } else if (ageHours > this.config.migration.hotAgeHours) {
      tier = "hot" /* HOT */;
      if (score < this.config.migration.scoreThreshold) {
        shouldMigrate = true;
        compressionLevel = "light";
      }
    }
    const localSizeLimit = 2 * 1024 * 1024 * 1024;
    const currentLocalSize = this.getLocalStorageSize();
    if (currentLocalSize > localSizeLimit * 0.75) {
      shouldMigrate = true;
      if (compressionLevel === "none") {
        compressionLevel = "light";
      }
    }
    return {
      traceId,
      age: ageHours,
      score,
      size,
      tier,
      shouldMigrate,
      compressionLevel
    };
  }
  /**
   * Migrate traces to remote storage
   */
  async migrateTraces(candidates, dryRun = false) {
    if (this.migrationInProgress) {
      return {
        migrated: 0,
        failed: 0,
        totalSize: 0,
        errors: ["Migration already in progress"]
      };
    }
    this.migrationInProgress = true;
    const results = {
      migrated: 0,
      failed: 0,
      totalSize: 0,
      errors: []
    };
    try {
      const toMigrate = candidates.filter((c) => c.shouldMigrate);
      const batches = this.createBatches(
        toMigrate,
        this.config.migration.batchSize
      );
      for (const batch of batches) {
        if (dryRun) {
          logger.info("Dry run - would migrate batch", {
            count: batch.length,
            totalSize: batch.reduce((sum, c) => sum + c.size, 0)
          });
          results.migrated += batch.length;
          continue;
        }
        const batchResults = await this.migrateBatch(batch);
        results.migrated += batchResults.success;
        results.failed += batchResults.failed;
        results.totalSize += batchResults.totalSize;
        results.errors.push(...batchResults.errors);
        await new Promise((resolve) => setTimeout(resolve, 100));
      }
    } finally {
      this.migrationInProgress = false;
    }
    logger.info("Migration completed", results);
    return results;
  }
  /**
   * Migrate a batch of traces
   */
  async migrateBatch(batch) {
    const results = {
      success: 0,
      failed: 0,
      totalSize: 0,
      errors: []
    };
    for (const candidate of batch) {
      try {
        const trace = this.getTraceData(candidate.traceId);
        if (!trace) {
          throw new Error(`Trace ${candidate.traceId} not found`);
        }
        const compressed = this.compressTrace(
          trace,
          candidate.compressionLevel
        );
        if (this.s3Client) {
          const s3Key = this.generateS3Key(candidate);
          await this.uploadToS3(s3Key, compressed);
          this.recordMigration(candidate, s3Key, trace, compressed);
        } else {
          this.recordMigration(candidate, "simulated", trace, compressed);
        }
        if (candidate.tier === StorageTier.COLD || candidate.tier === "archive" /* ARCHIVE */) {
          this.removeLocalTrace(candidate.traceId);
        }
        results.success++;
        results.totalSize += candidate.size;
      } catch (error) {
        results.failed++;
        results.errors.push(`Failed to migrate ${candidate.traceId}: ${error}`);
        logger.error("Migration failed for trace", {
          traceId: candidate.traceId,
          error
        });
      }
    }
    return results;
  }
  /**
   * Get full trace data for migration
   */
  getTraceData(traceId) {
    const traceRow = this.localDb.prepare("SELECT * FROM traces WHERE id = ?").get(traceId);
    if (!traceRow) return null;
    const toolCalls = this.localDb.prepare(
      "SELECT * FROM tool_calls WHERE trace_id = ? ORDER BY sequence_number"
    ).all(traceId);
    return {
      trace: traceRow,
      toolCalls
    };
  }
  /**
   * Compress trace based on compression level
   */
  compressTrace(data, level) {
    const jsonData = JSON.stringify(data);
    switch (level) {
      case "none":
        return Buffer.from(jsonData);
      case "light":
        return Buffer.from(JSON.stringify(JSON.parse(jsonData)));
      case "medium":
        const cleaned = JSON.parse(
          jsonData,
          (key, value) => value === null || value === void 0 ? void 0 : value
        );
        return Buffer.from(JSON.stringify(cleaned));
      case "heavy":
        const minimal = {
          id: data.trace.id,
          type: data.trace.type,
          score: data.trace.score,
          summary: data.trace.summary,
          timestamps: {
            start: data.trace.start_time,
            end: data.trace.end_time
          },
          toolCount: data.toolCalls.length,
          toolTypes: [...new Set(data.toolCalls.map((t) => t.tool))]
        };
        return Buffer.from(JSON.stringify(minimal));
      default:
        return Buffer.from(jsonData);
    }
  }
  /**
   * Generate S3 key for trace
   */
  generateS3Key(candidate) {
    const date = new Date(Date.now() - candidate.age * 60 * 60 * 1e3);
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, "0");
    const day = String(date.getDate()).padStart(2, "0");
    return `traces/${year}/${month}/${day}/${candidate.tier}/${candidate.traceId}.json`;
  }
  /**
   * Upload to S3
   */
  async uploadToS3(key, data) {
    if (!this.s3Client) {
      throw new Error("S3 client not configured");
    }
    const command = new PutObjectCommand({
      Bucket: this.config.s3.bucket,
      Key: key,
      Body: data,
      ContentType: "application/json",
      Metadata: {
        "trace-version": "1.0",
        compression: "true"
      }
    });
    await this.s3Client.send(command);
  }
  /**
   * Record migration in local database
   */
  recordMigration(candidate, s3Key, originalData, compressedData) {
    const stmt = this.localDb.prepare(`
      INSERT INTO remote_migrations (
        trace_id, migrated_at, storage_tier, s3_key,
        compression_level, original_size, compressed_size
      ) VALUES (?, ?, ?, ?, ?, ?, ?)
    `);
    stmt.run(
      candidate.traceId,
      Date.now(),
      candidate.tier,
      s3Key,
      candidate.compressionLevel,
      JSON.stringify(originalData).length,
      compressedData.length
    );
  }
  /**
   * Remove local trace after migration
   */
  removeLocalTrace(traceId) {
    this.localDb.prepare("DELETE FROM tool_calls WHERE trace_id = ?").run(traceId);
    this.localDb.prepare("DELETE FROM traces WHERE id = ?").run(traceId);
  }
  /**
   * Get current local storage size
   */
  getLocalStorageSize() {
    const result = this.localDb.prepare(
      `
      SELECT 
        SUM(LENGTH(compressed_data)) +
        COALESCE((SELECT SUM(LENGTH(arguments) + LENGTH(result)) 
                  FROM tool_calls), 0) as total_size
      FROM traces
    `
    ).get();
    return result?.total_size || 0;
  }
  /**
   * Create batches from candidates
   */
  createBatches(items, batchSize) {
    const batches = [];
    for (let i = 0; i < items.length; i += batchSize) {
      batches.push(items.slice(i, i + batchSize));
    }
    return batches;
  }
  /**
   * Retrieve trace from remote storage
   */
  async retrieveTrace(traceId) {
    const migration = this.localDb.prepare(
      `
      SELECT * FROM remote_migrations WHERE trace_id = ?
    `
    ).get(traceId);
    if (!migration) {
      throw new Error(`Trace ${traceId} not found in remote storage`);
    }
    this.localDb.prepare(
      `
      UPDATE remote_migrations 
      SET retrieval_count = retrieval_count + 1, last_retrieved = ?
      WHERE trace_id = ?
    `
    ).run(Date.now(), traceId);
    if (!this.s3Client) {
      throw new Error("S3 client not configured");
    }
    const command = new GetObjectCommand({
      Bucket: this.config.s3.bucket,
      Key: migration.s3_key
    });
    const response = await this.s3Client.send(command);
    const data = await response.Body?.transformToString();
    if (!data) {
      throw new Error("No data retrieved from S3");
    }
    return JSON.parse(data);
  }
  /**
   * Get migration statistics
   */
  getMigrationStats() {
    const stats = this.localDb.prepare(
      `
      SELECT 
        storage_tier,
        COUNT(*) as count,
        SUM(original_size) as original_size,
        SUM(compressed_size) as compressed_size,
        AVG(retrieval_count) as avg_retrievals
      FROM remote_migrations
      GROUP BY storage_tier
    `
    ).all();
    const total = this.localDb.prepare(
      `
      SELECT 
        COUNT(*) as total_migrated,
        SUM(original_size) as total_original,
        SUM(compressed_size) as total_compressed
      FROM remote_migrations
    `
    ).get();
    return {
      byTier: stats,
      total,
      compressionRatio: total ? (1 - total.total_compressed / total.total_original).toFixed(2) : 0,
      localSize: this.getLocalStorageSize()
    };
  }
}
export {
  DEFAULT_REMOTE_CONFIG,
  RemoteStorageManager,
  StorageTier
};
//# sourceMappingURL=remote-storage.js.map
