/**
 * POST /api/eterra/export-layer-gpkg (v2 — sync-first)
 *
 * Flow:
 *   1. Check local DB freshness for the requested layer
 *   2. If stale/empty → sync from eTerra (stores in DB)
 *   3. Build GPKG from local DB
 *   4. Return GPKG
 *
 * Body: { username?, password?, siruta, layerId, jobId?, forceSync? }
 */
import { prisma } from "@/core/storage/prisma";
import { findLayerById } from "@/modules/parcel-sync/services/eterra-layers";
import { getEpsg3844Wkt } from "@/modules/parcel-sync/services/reproject";
import { buildGpkg } from "@/modules/parcel-sync/services/gpkg-export";
import { syncLayer } from "@/modules/parcel-sync/services/sync-service";
import {
  getLayerFreshness,
  isFresh,
} from "@/modules/parcel-sync/services/enrich-service";
import {
  clearProgress,
  setProgress,
} from "@/modules/parcel-sync/services/progress-store";
import {
  getSessionCredentials,
  registerJob,
  unregisterJob,
} from "@/modules/parcel-sync/services/session-store";
import type { GeoJsonFeature } from "@/modules/parcel-sync/services/esri-geojson";

export const runtime = "nodejs";
export const dynamic = "force-dynamic";

type ExportLayerRequest = {
  username?: string;
  password?: string;
  siruta?: string | number;
  layerId?: string;
  jobId?: string;
  forceSync?: boolean;
};

const validate = (body: ExportLayerRequest) => {
  const session = getSessionCredentials();
  const username = String(
    body.username || session?.username || process.env.ETERRA_USERNAME || "",
  ).trim();
  const password = String(
    body.password || session?.password || process.env.ETERRA_PASSWORD || "",
  ).trim();
  const siruta = String(body.siruta ?? "").trim();
  const layerId = String(body.layerId ?? "").trim();
  const jobId = body.jobId ? String(body.jobId).trim() : undefined;
  const forceSync = body.forceSync === true;

  if (!username) throw new Error("Email is required");
  if (!password) throw new Error("Password is required");
  if (!/^\d+$/.test(siruta)) throw new Error("SIRUTA must be numeric");
  if (!layerId) throw new Error("Layer ID missing");

  return { username, password, siruta, layerId, jobId, forceSync };
};

const scheduleClear = (jobId?: string) => {
  if (!jobId) return;
  setTimeout(() => clearProgress(jobId), 60_000);
};

export async function POST(req: Request) {
  let jobId: string | undefined;
  let message: string | undefined;
  let phase = "Inițializare";
  let note: string | undefined;
  let status: "running" | "done" | "error" = "running";
  let downloaded = 0;
  let total: number | undefined;
  let completedWeight = 0;
  let currentWeight = 0;
  let phaseTotal: number | undefined;
  let phaseCurrent: number | undefined;

  const pushProgress = () => {
    if (!jobId) return;
    setProgress({
      jobId,
      downloaded,
      total,
      status,
      phase,
      note,
      message,
      phaseCurrent,
      phaseTotal,
    });
  };

  const updateOverall = (fraction = 0) => {
    const overall = completedWeight + currentWeight * fraction;
    downloaded = Number(Math.min(100, Math.max(0, overall)).toFixed(1));
    total = 100;
    pushProgress();
  };

  const setPhaseState = (next: string, weight: number, nextTotal?: number) => {
    phase = next;
    currentWeight = weight;
    phaseTotal = nextTotal;
    phaseCurrent = nextTotal ? 0 : undefined;
    note = undefined;
    updateOverall(0);
  };

  const updatePhaseProgress = (value: number, nextTotal?: number) => {
    if (typeof nextTotal === "number") phaseTotal = nextTotal;
    if (phaseTotal && phaseTotal > 0) {
      phaseCurrent = value;
      updateOverall(Math.min(1, value / phaseTotal));
    } else {
      phaseCurrent = undefined;
      updateOverall(0);
    }
  };

  const finishPhase = () => {
    completedWeight += currentWeight;
    currentWeight = 0;
    phaseTotal = undefined;
    phaseCurrent = undefined;
    note = undefined;
    updateOverall(0);
  };

  const withHeartbeat = async <T>(task: () => Promise<T>) => {
    let tick = 0.1;
    updatePhaseProgress(tick, 1);
    const interval = setInterval(() => {
      tick = Math.min(0.9, tick + 0.05);
      updatePhaseProgress(tick, 1);
    }, 1200);
    try {
      return await task();
    } finally {
      clearInterval(interval);
    }
  };

  try {
    const body = (await req.json()) as ExportLayerRequest;
    const validated = validate(body);
    jobId = validated.jobId;
    if (jobId) registerJob(jobId);
    pushProgress();

    const layer = findLayerById(validated.layerId);
    if (!layer) throw new Error("Layer not configured");

    const weights = { sync: 60, gpkg: 30, finalize: 10 };

    /* ── Phase 1: Check freshness & sync if needed ── */
    setPhaseState("Verificare date locale", weights.sync, 1);

    const freshness = await getLayerFreshness(
      validated.siruta,
      validated.layerId,
    );
    const needsSync =
      validated.forceSync ||
      !isFresh(freshness.lastSynced) ||
      freshness.featureCount === 0;

    let syncedFromCache = true;

    if (needsSync) {
      syncedFromCache = false;
      phase = `Sincronizare ${layer.name}`;
      note =
        freshness.featureCount > 0
          ? "Re-sync (date expirate)"
          : "Sync inițial de la eTerra";
      pushProgress();

      const syncResult = await syncLayer(
        validated.username,
        validated.password,
        validated.siruta,
        validated.layerId,
        { forceFullSync: validated.forceSync, jobId, isSubStep: true },
      );
      if (syncResult.status === "error") {
        throw new Error(syncResult.error ?? "Sync failed");
      }
    } else {
      note = "Date proaspete în baza de date — skip sync";
      pushProgress();
    }
    updatePhaseProgress(1, 1);
    finishPhase();

    /* ── Phase 2: Build GPKG from local DB ── */
    // Take back progress control after syncLayer
    setPhaseState("Generare GPKG din baza de date", weights.gpkg, 1);

    const features = await prisma.gisFeature.findMany({
      where: { layerId: validated.layerId, siruta: validated.siruta },
      select: { attributes: true, geometry: true },
    });

    if (features.length === 0) {
      throw new Error(
        `Niciun feature în DB pentru ${layer.name} / SIRUTA ${validated.siruta}`,
      );
    }

    const geoFeatures: GeoJsonFeature[] = features
      .filter((f) => f.geometry != null)
      .map((f) => ({
        type: "Feature" as const,
        geometry: f.geometry as GeoJsonFeature["geometry"],
        properties: f.attributes as Record<string, unknown>,
      }));

    const fields = Object.keys(geoFeatures[0]?.properties ?? {});

    const gpkg = await withHeartbeat(() =>
      buildGpkg({
        srsId: 3844,
        srsWkt: getEpsg3844Wkt(),
        layers: [{ name: layer.name, fields, features: geoFeatures }],
      }),
    );
    updatePhaseProgress(1, 1);
    finishPhase();

    /* ── Phase 3: Finalize ── */
    setPhaseState("Finalizare", weights.finalize, 1);
    updatePhaseProgress(1, 1);
    finishPhase();

    const suffix = syncedFromCache ? " (din cache local)" : "";
    status = "done";
    phase = "Finalizat";
    message = `Finalizat 100% · ${geoFeatures.length} elemente${suffix}`;
    pushProgress();
    scheduleClear(jobId);

    if (jobId) unregisterJob(jobId);
    const filename = `eterra_uat_${validated.siruta}_${layer.name}.gpkg`;
    return new Response(new Uint8Array(gpkg), {
      headers: {
        "Content-Type": "application/geopackage+sqlite3",
        "Content-Disposition": `attachment; filename="${filename}"`,
      },
    });
  } catch (error) {
    const errMessage =
      error instanceof Error ? error.message : "Unexpected server error";
    status = "error";
    message = errMessage;
    note = undefined;
    pushProgress();
    scheduleClear(jobId);
    if (jobId) unregisterJob(jobId);
    const lower = errMessage.toLowerCase();
    const statusCode =
      lower.includes("login failed") || lower.includes("session") ? 401 : 400;
    return new Response(JSON.stringify({ error: errMessage }), {
      status: statusCode,
      headers: { "Content-Type": "application/json" },
    });
  }
}
