/**
 * Migrate legacy local `public/uploads` files to Cloudflare R2 and update Mongo URLs.
 *
 * Usage:
 *   npx ts-node src/scripts/migrateUploadsToR2.ts           # dry-run
 *   npx ts-node src/scripts/migrateUploadsToR2.ts --apply   # write changes
 *
 * Requires R2_* env vars (see docs/r2-storage-frontend-README.md).
 */
import path from "path";
import fs from "fs";
import dotenv from "dotenv";
import mongoose from "mongoose";

dotenv.config({ path: path.resolve(process.cwd(), ".env"), override: false });
dotenv.config({
  path: path.resolve(process.cwd(), "src", "config", ".env"),
  override: false,
});

import { MONGODB_CONNECTION_STRING } from "../config/environment";
import { uploadBufferToR2 } from "../services/r2StorageService";
import UserModel from "../models/UserModel";
import Trip from "../models/TripModel";
import GasStationModel from "../models/GasStationModel";
import MessageModel from "../models/MessageModel";

const APPLY = process.argv.includes("--apply");
const UPLOADS_DIR = path.resolve(process.cwd(), "public", "uploads");

function isHttp(url: string): boolean {
  return /^https?:\/\//i.test(url);
}

function bareFilename(stored: string): string | null {
  const t = stored.trim();
  if (!t || isHttp(t)) return null;
  let relative = t.replace(/^\/+/, "");
  if (relative.toLowerCase().startsWith("public/")) {
    relative = relative.slice("public/".length);
  }
  if (relative.toLowerCase().startsWith("uploads/")) {
    relative = relative.slice("uploads/".length);
  }
  return relative || null;
}

function resolveLocalFile(stored: string): string | null {
  const bare = bareFilename(stored);
  if (!bare) return null;
  const full = path.join(UPLOADS_DIR, bare);
  if (fs.existsSync(full) && fs.statSync(full).isFile()) return full;
  return null;
}

function guessContentType(filePath: string): string {
  const ext = path.extname(filePath).toLowerCase();
  const map: Record<string, string> = {
    ".jpg": "image/jpeg",
    ".jpeg": "image/jpeg",
    ".png": "image/png",
    ".gif": "image/gif",
    ".webp": "image/webp",
    ".jfif": "image/jpeg",
    ".mp4": "video/mp4",
    ".mov": "video/quicktime",
    ".webm": "video/webm",
  };
  return map[ext] ?? "application/octet-stream";
}

async function migrateStoredPath(
  stored: string | null | undefined,
  folder: string,
): Promise<{ next: string | null; changed: boolean; skipped: string | null }> {
  if (!stored || typeof stored !== "string") {
    return { next: null, changed: false, skipped: null };
  }
  const trimmed = stored.trim();
  if (!trimmed) return { next: null, changed: false, skipped: null };
  if (isHttp(trimmed)) {
    return { next: trimmed, changed: false, skipped: "already-url" };
  }
  const local = resolveLocalFile(trimmed);
  if (!local) {
    return { next: trimmed, changed: false, skipped: "file-missing" };
  }
  const buffer = fs.readFileSync(local);
  const { url } = await uploadBufferToR2({
    buffer,
    contentType: guessContentType(local),
    folder,
    filename: path.basename(local),
  });
  return { next: url, changed: true, skipped: null };
}

type Stats = {
  scanned: number;
  updated: number;
  skippedAlreadyUrl: number;
  skippedMissing: number;
  errors: number;
};

function emptyStats(): Stats {
  return {
    scanned: 0,
    updated: 0,
    skippedAlreadyUrl: 0,
    skippedMissing: 0,
    errors: 0,
  };
}

async function main() {
  const uri = MONGODB_CONNECTION_STRING;
  if (!uri) {
    console.error("Missing DB_URI / MONGODB_CONNECTION_STRING");
    process.exit(1);
  }

  console.log(`Mode: ${APPLY ? "APPLY" : "DRY-RUN (pass --apply to write)"}`);
  console.log(`Uploads dir: ${UPLOADS_DIR}`);
  console.log(`Exists: ${fs.existsSync(UPLOADS_DIR)}`);

  await mongoose.connect(uri);

  const stats = {
    users: emptyStats(),
    trips: emptyStats(),
    stations: emptyStats(),
    messages: emptyStats(),
  };

  // Users
  const users = await UserModel.find({
    image: { $exists: true, $nin: [null, ""] },
  }).select("_id image");
  for (const u of users) {
    stats.users.scanned++;
    try {
      const r = await migrateStoredPath(u.image, "profiles");
      if (r.skipped === "already-url") stats.users.skippedAlreadyUrl++;
      if (r.skipped === "file-missing") stats.users.skippedMissing++;
      if (r.changed) {
        console.log(`[user ${u._id}] ${u.image} -> ${r.next}`);
        if (APPLY) {
          await UserModel.updateOne(
            { _id: u._id },
            { $set: { image: r.next } },
          );
        }
        stats.users.updated++;
      }
    } catch (e) {
      stats.users.errors++;
      console.error(`[user ${u._id}]`, e);
    }
  }

  // Trips
  const trips = await Trip.find({
    imageUrl: { $exists: true, $nin: [null, ""] },
  }).select("_id imageUrl");
  for (const t of trips) {
    stats.trips.scanned++;
    try {
      const r = await migrateStoredPath(t.imageUrl as string, "trips");
      if (r.skipped === "already-url") stats.trips.skippedAlreadyUrl++;
      if (r.skipped === "file-missing") stats.trips.skippedMissing++;
      if (r.changed) {
        console.log(`[trip ${t._id}] ${t.imageUrl} -> ${r.next}`);
        if (APPLY) {
          await Trip.updateOne({ _id: t._id }, { $set: { imageUrl: r.next } });
        }
        stats.trips.updated++;
      }
    } catch (e) {
      stats.trips.errors++;
      console.error(`[trip ${t._id}]`, e);
    }
  }

  // Gas stations
  const stations = await GasStationModel.find({
    $or: [
      { mainImage: { $exists: true, $nin: [null, ""] } },
      { logoUrl: { $exists: true, $nin: [null, ""] } },
      { galleryImages: { $exists: true, $ne: [] } },
    ],
  }).select("_id mainImage logoUrl galleryImages");
  for (const s of stations) {
    stats.stations.scanned++;
    try {
      const set: Record<string, unknown> = {};
      let changed = false;

      const main = await migrateStoredPath(s.mainImage, "stations");
      if (main.skipped === "already-url") stats.stations.skippedAlreadyUrl++;
      if (main.skipped === "file-missing") stats.stations.skippedMissing++;
      if (main.changed && main.next) {
        set.mainImage = main.next;
        changed = true;
        console.log(`[station ${s._id}] mainImage -> ${main.next}`);
      }

      const logo = await migrateStoredPath(s.logoUrl, "stations");
      if (logo.skipped === "already-url") stats.stations.skippedAlreadyUrl++;
      if (logo.skipped === "file-missing") stats.stations.skippedMissing++;
      if (logo.changed && logo.next) {
        set.logoUrl = logo.next;
        changed = true;
        console.log(`[station ${s._id}] logoUrl -> ${logo.next}`);
      }

      const gallery = s.galleryImages ?? [];
      const nextGallery: string[] = [];
      let galleryChanged = false;
      for (const g of gallery) {
        const r = await migrateStoredPath(g, "stations");
        if (r.skipped === "already-url") stats.stations.skippedAlreadyUrl++;
        if (r.skipped === "file-missing") stats.stations.skippedMissing++;
        nextGallery.push(r.next ?? g);
        if (r.changed) galleryChanged = true;
      }
      if (galleryChanged) {
        set.galleryImages = nextGallery;
        changed = true;
        console.log(`[station ${s._id}] galleryImages updated`);
      }

      if (changed) {
        if (APPLY) {
          await GasStationModel.updateOne({ _id: s._id }, { $set: set });
        }
        stats.stations.updated++;
      }
    } catch (e) {
      stats.stations.errors++;
      console.error(`[station ${s._id}]`, e);
    }
  }

  // Chat messages with media
  const messages = await MessageModel.find({
    "media.0": { $exists: true },
  }).select("_id media");
  for (const m of messages) {
    stats.messages.scanned++;
    try {
      const media = (m.media ?? []) as Array<{ url?: string }>;
      let changed = false;
      const nextMedia = [];
      for (const item of media) {
        const r = await migrateStoredPath(item.url, "chat");
        if (r.skipped === "already-url") stats.messages.skippedAlreadyUrl++;
        if (r.skipped === "file-missing") stats.messages.skippedMissing++;
        if (r.changed) {
          changed = true;
          console.log(`[message ${m._id}] ${item.url} -> ${r.next}`);
        }
        nextMedia.push({ ...item, url: r.next ?? item.url });
      }
      if (changed) {
        if (APPLY) {
          await MessageModel.updateOne(
            { _id: m._id },
            { $set: { media: nextMedia } },
          );
        }
        stats.messages.updated++;
      }
    } catch (e) {
      stats.messages.errors++;
      console.error(`[message ${m._id}]`, e);
    }
  }

  console.log("\nStats:", JSON.stringify(stats, null, 2));
  await mongoose.disconnect();
}

main().catch((e) => {
  console.error(e);
  process.exit(1);
});
