/**
 * Shadow-capture: sparar varje lyckad AI-inferens som träningsdata i S3.
 *
 * - Kör aldrig synkront på användarens kritiska väg; failures swallås.
 * - Ingen PII (inga userId/householdId). Endast S3-nycklar, output och metadata.
 * - Rå bild kopieras ENDAST när imageTraining-samtycke är true.
 * - Durabelt = S3 i prod, lokalt filsystem i mock/test.
 */
import { randomUUID } from "node:crypto";
import { mkdir, writeFile } from "node:fs/promises";
import path from "node:path";
import { PutObjectCommand, S3Client, type S3ClientConfig } from "@aws-sdk/client-s3";
import type { AamosTaskType } from "@app/ai-contracts";

export interface CaptureConsentFlags {
  personalization: boolean;
  anonymizedImprovement: boolean;
  imageTraining: boolean;
}

export interface CaptureResult {
  ok: boolean;
  key?: string;
  error?: string;
}

export interface CaptureInput {
  taskType: AamosTaskType;
  inputS3Keys: string[];
  output: Record<string, unknown>;
  modelVersion?: string | null;
  promptVersion?: string | null;
  latencyMs?: number | null;
  costUsd?: number | null;
  inputTokens?: number | null;
  outputTokens?: number | null;
  consentFlags: CaptureConsentFlags;
  readUrl: (key: string) => Promise<string>;
  /** Override for tests; otherwise built from env. */
  storage?: CaptureStorage;
}

export interface CaptureStorage {
  put(key: string, data: Buffer, contentType: string): Promise<void>;
}

const TRAINING_PREFIX = "training/cibello";
const MOCK_ROOT = path.resolve(process.cwd(), ".data/s3-training");

class S3CaptureStorage implements CaptureStorage {
  private readonly client: S3Client;
  private readonly bucket: string;

  constructor(bucket: string, config: S3ClientConfig) {
    this.bucket = bucket;
    this.client = new S3Client(config);
  }

  async put(key: string, data: Buffer, contentType: string): Promise<void> {
    await this.client.send(
      new PutObjectCommand({
        Bucket: this.bucket,
        Key: key,
        Body: data,
        ContentType: contentType,
      }),
    );
  }
}

class LocalCaptureStorage implements CaptureStorage {
  async put(key: string, data: Buffer): Promise<void> {
    const filePath = path.join(MOCK_ROOT, key);
    await mkdir(path.dirname(filePath), { recursive: true });
    await writeFile(filePath, data);
  }
}

let cachedStorage: CaptureStorage | undefined;

export function createCaptureStorage(): CaptureStorage {
  if (cachedStorage) return cachedStorage;

  const mode = process.env.S3_MODE ?? "mock";
  if (mode === "aws") {
    const bucket = process.env.S3_BUCKET ?? "cibello-production";
    const config: S3ClientConfig = {
      region: process.env.S3_REGION || "eu-north-1",
      ...(process.env.S3_ENDPOINT
        ? { endpoint: process.env.S3_ENDPOINT, forcePathStyle: true }
        : {}),
      ...(process.env.S3_ACCESS_KEY_ID && process.env.S3_SECRET_ACCESS_KEY
        ? {
            credentials: {
              accessKeyId: process.env.S3_ACCESS_KEY_ID,
              secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
            },
          }
        : {}),
    };
    cachedStorage = new S3CaptureStorage(bucket, config);
  } else {
    cachedStorage = new LocalCaptureStorage();
  }
  return cachedStorage;
}

export async function captureTrainingSample(input: CaptureInput): Promise<CaptureResult> {
  try {
    const storage = input.storage ?? createCaptureStorage();
    const date = new Date().toISOString().slice(0, 10);
    const id = randomUUID();
    const prefix = `${TRAINING_PREFIX}/${input.taskType}/${date}/${id}`;
    const jsonKey = `${prefix}/sample.json`;

    const sample: Record<string, unknown> = {
      taskType: input.taskType,
      inputReferences: input.inputS3Keys,
      output: input.output,
      modelVersion: input.modelVersion ?? null,
      promptVersion: input.promptVersion ?? null,
      latencyMs: input.latencyMs ?? null,
      costUsd: input.costUsd ?? null,
      inputTokens: input.inputTokens ?? null,
      outputTokens: input.outputTokens ?? null,
      consentFlags: input.consentFlags,
      capturedAt: new Date().toISOString(),
    };

    const trainingImageKeys: string[] = [];
    if (input.consentFlags.imageTraining && input.inputS3Keys.length > 0) {
      for (let i = 0; i < input.inputS3Keys.length; i++) {
        const originalKey = input.inputS3Keys[i];
        if (!originalKey) continue;
        try {
          const imageKey = `${prefix}/images/${String(i).padStart(3, "0")}.jpg`;
          const imageData = await fetchImageData(await input.readUrl(originalKey));
          if (imageData) {
            await storage.put(imageKey, imageData, "image/jpeg");
            trainingImageKeys.push(imageKey);
          }
        } catch (err) {
          // Best effort: log but continue; JSON still has original S3 references.
          console.error(`[shadow-capture] failed to copy image ${originalKey}:`, err);
        }
      }
    }

    if (trainingImageKeys.length > 0) {
      sample.trainingImageKeys = trainingImageKeys;
    }

    await storage.put(jsonKey, Buffer.from(JSON.stringify(sample, null, 2)), "application/json");
    return { ok: true, key: jsonKey };
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    console.error("[shadow-capture] failed:", message);
    return { ok: false, error: message };
  }
}

async function fetchImageData(url: string): Promise<Buffer | null> {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), 30_000);
  try {
    const res = await fetch(url, { signal: controller.signal });
    clearTimeout(timer);
    if (!res.ok) return null;
    return Buffer.from(await res.arrayBuffer());
  } catch {
    return null;
  } finally {
    clearTimeout(timer);
  }
}
