import { NextResponse } from "next/server";
import { prisma } from "@/core/storage/prisma";
import { readFile } from "fs/promises";
import { join } from "path";
import { EterraClient } from "@/modules/parcel-sync/services/eterra-client";
import { getSessionCredentials } from "@/modules/parcel-sync/services/session-store";

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

/* ------------------------------------------------------------------ */
/*  Feature count cache (expensive query, cached 5 min)                */
/* ------------------------------------------------------------------ */

const gCache = globalThis as {
  __featureCountCache?: { map: Map<string, number>; ts: number };
};

async function getCachedFeatureCounts(): Promise<Map<string, number>> {
  const TTL = 5 * 60 * 1000; // 5 minutes
  const now = Date.now();

  if (gCache.__featureCountCache && now - gCache.__featureCountCache.ts < TTL) {
    return gCache.__featureCountCache.map;
  }

  // Run in background if cache exists but expired (return stale, refresh async)
  if (gCache.__featureCountCache) {
    void refreshFeatureCounts();
    return gCache.__featureCountCache.map;
  }

  // First call: must wait
  return refreshFeatureCounts();
}

async function refreshFeatureCounts(): Promise<Map<string, number>> {
  try {
    const groups = await prisma.gisFeature.groupBy({
      by: ["siruta"],
      _count: { id: true },
    });
    const map = new Map<string, number>();
    for (const g of groups) {
      map.set(g.siruta, g._count.id);
    }
    gCache.__featureCountCache = { map, ts: Date.now() };
    return map;
  } catch {
    return gCache.__featureCountCache?.map ?? new Map();
  }
}

/* ------------------------------------------------------------------ */
/*  Types                                                              */
/* ------------------------------------------------------------------ */

type UatResponse = {
  siruta: string;
  name: string;
  county: string;
  workspacePk: number;
  /** Number of GIS features synced locally for this UAT */
  localFeatures: number;
};

/* ------------------------------------------------------------------ */
/*  Helpers                                                            */
/* ------------------------------------------------------------------ */

function populateWorkspaceCache(
  uats: Array<{ siruta: string; workspacePk: number }>,
) {
  const wsGlobal = globalThis as {
    __eterraWorkspaceCache?: Map<string, number>;
  };
  if (!wsGlobal.__eterraWorkspaceCache) {
    wsGlobal.__eterraWorkspaceCache = new Map();
  }
  for (const u of uats) {
    if (u.workspacePk) {
      wsGlobal.__eterraWorkspaceCache.set(u.siruta, u.workspacePk);
    }
  }
}

/** Remove diacritics and uppercase for fuzzy name matching */
function normalizeName(s: string): string {
  return s
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .toUpperCase()
    .trim();
}

/** Title-case: "SATU MARE" → "Satu Mare" */
function titleCase(s: string): string {
  return s
    .toLowerCase()
    .replace(/(?:^|\s)\S/g, (ch) => ch.toUpperCase());
}

/**
 * Extract a name from an eTerra nomenclature entry.
 * Tries multiple possible field names.
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function extractName(entry: any): string {
  if (!entry || typeof entry !== "object") return "";
  for (const key of ["name", "nomenName", "label", "denumire", "NAME"]) {
    const val = entry[key];
    if (typeof val === "string" && val.trim()) return val.trim();
  }
  return "";
}

/**
 * Extract a SIRUTA code from an eTerra nomenclature entry.
 * Tries multiple possible field names (nomenPk ≠ SIRUTA, but code might be).
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function extractCode(entry: any): string {
  if (!entry || typeof entry !== "object") return "";
  for (const key of [
    "code",
    "sirutaCode",
    "siruta",
    "externalCode",
    "cod",
    "CODE",
  ]) {
    const val = entry[key];
    if (val != null) {
      const s = String(val).trim();
      if (s && /^\d+$/.test(s)) return s;
    }
  }
  return "";
}

/**
 * Unwrap a potentially nested response (Spring Boot Page format).
 * eTerra sometimes returns {content: [...]} instead of flat arrays.
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function unwrapArray(data: any): any[] {
  if (Array.isArray(data)) return data;
  if (data && typeof data === "object") {
    if (Array.isArray(data.content)) return data.content;
    if (Array.isArray(data.data)) return data.data;
    if (Array.isArray(data.items)) return data.items;
    if (Array.isArray(data.results)) return data.results;
  }
  return [];
}

/* ------------------------------------------------------------------ */
/*  GET /api/eterra/uats                                               */
/*                                                                     */
/*  Always serves from local PostgreSQL (GisUat table).                */
/*  Includes local GIS feature counts per UAT for the UI indicator.    */
/*  No eTerra credentials needed — instant response.                   */
/* ------------------------------------------------------------------ */

export async function GET() {
  try {
    // CRITICAL: select only needed fields — geometry column has huge polygon data
    const rows = await prisma.gisUat.findMany({
      orderBy: { name: "asc" },
      select: { siruta: true, name: true, county: true, workspacePk: true },
    });

    // Feature counts: use in-memory cache (refreshed every 5 min)
    // The groupBy query is expensive (~25s without cache) but the data
    // changes rarely (only when sync jobs run)
    const featureCounts = await getCachedFeatureCounts();

    const uats: UatResponse[] = rows.map((r) => ({
      siruta: r.siruta,
      name: r.name,
      county: r.county ?? "",
      workspacePk: r.workspacePk ?? 0,
      localFeatures: featureCounts.get(r.siruta) ?? 0,
    }));

    // Populate in-memory workspace cache for search route
    if (uats.length > 0) {
      populateWorkspaceCache(uats);
    }

    return NextResponse.json({
      uats,
      total: uats.length,
      source: "database",
    });
  } catch (error) {
    const message = error instanceof Error ? error.message : "Eroare server";
    return NextResponse.json({ error: message, uats: [] }, { status: 500 });
  }
}

/* ------------------------------------------------------------------ */
/*  POST /api/eterra/uats                                              */
/*                                                                     */
/*  Seed or resync DB from static uat.json.                            */
/*  Uses upsert so it's safe to call repeatedly — new UATs are added,  */
/*  existing names are updated, county/workspacePk are preserved.      */
/*  eTerra nomenPk ≠ SIRUTA, so we cannot use the nomenclature API     */
/*  for populating UAT data. uat.json has correct SIRUTA codes.        */
/*  Workspace (county) PKs are resolved lazily via ArcGIS layer query  */
/*  in the search route and persisted to DB on first resolution.       */
/* ------------------------------------------------------------------ */

export async function POST() {
  try {

    // Read uat.json from public/ directory
    let rawUats: Array<{ siruta: string; name: string }>;
    try {
      const filePath = join(process.cwd(), "public", "uat.json");
      const content = await readFile(filePath, "utf-8");
      rawUats = JSON.parse(content);
    } catch {
      return NextResponse.json(
        { error: "Nu s-a putut citi uat.json", synced: false },
        { status: 500 },
      );
    }

    if (!Array.isArray(rawUats) || rawUats.length === 0) {
      return NextResponse.json({
        synced: false,
        reason: "empty-uat-json",
        total: 0,
      });
    }

    // Batch insert in chunks of 500 (Prisma transaction limit)
    const CHUNK_SIZE = 500;
    let inserted = 0;
    for (let i = 0; i < rawUats.length; i += CHUNK_SIZE) {
      const chunk = rawUats.slice(i, i + CHUNK_SIZE);
      await prisma.$transaction(
        chunk
          .filter((u) => u.siruta && u.name)
          .map((u) =>
            prisma.gisUat.upsert({
              where: { siruta: String(u.siruta) },
              update: { name: String(u.name).trim() },
              create: {
                siruta: String(u.siruta),
                name: String(u.name).trim(),
              },
            }),
          ),
      );
      inserted += chunk.length;
    }

    console.log(`[uats] Seeded ${inserted} UATs from uat.json`);

    return NextResponse.json({
      synced: true,
      total: inserted,
      source: "uat-json",
    });
  } catch (error) {
    const message = error instanceof Error ? error.message : "Eroare server";
    return NextResponse.json(
      { error: message, synced: false },
      { status: 500 },
    );
  }
}

/* ------------------------------------------------------------------ */
/*  PATCH /api/eterra/uats                                             */
/*                                                                     */
/*  Populate county names from eTerra nomenclature API.                */
/*                                                                     */
/*  Strategy (two phases):                                             */
/*  Phase 1: For UATs that already have workspacePk resolved,          */
/*    use fetchCounties() → countyMap[workspacePk] → instant update.   */
/*  Phase 2: For remaining UATs, enumerate counties →                  */
/*    fetchAdminUnitsByCounty() per county → match by code or name.    */
/*                                                                     */
/*  Requires active eTerra session.                                    */
/* ------------------------------------------------------------------ */

export async function PATCH() {
  try {
    // 1. Get eTerra credentials from session
    const session = getSessionCredentials();
    const username = String(
      session?.username || process.env.ETERRA_USERNAME || "",
    ).trim();
    const password = String(
      session?.password || process.env.ETERRA_PASSWORD || "",
    ).trim();

    if (!username || !password) {
      return NextResponse.json(
        { error: "Conectează-te la eTerra mai întâi." },
        { status: 401 },
      );
    }

    const client = await EterraClient.create(username, password);

    // 2. Fetch all counties from eTerra nomenclature
    const rawCounties = await client.fetchCounties();
    const counties = unwrapArray(rawCounties);
    const countyMap = new Map<number, string>(); // nomenPk → county name
    for (const c of counties) {
      const pk = Number(c?.nomenPk ?? 0);
      const name = extractName(c);
      if (pk > 0 && name) {
        countyMap.set(pk, titleCase(name));
      }
    }

    if (countyMap.size === 0) {
      // Log raw response for debugging
      console.error(
        "[uats-patch] fetchCounties returned 0 counties. Raw sample:",
        JSON.stringify(rawCounties).slice(0, 500),
      );
      return NextResponse.json(
        {
          error: "Nu s-au putut obține județele din eTerra.",
          debug: {
            rawType: typeof rawCounties,
            isArray: Array.isArray(rawCounties),
            length: Array.isArray(rawCounties) ? rawCounties.length : null,
            sample: JSON.stringify(rawCounties).slice(0, 300),
          },
        },
        { status: 502 },
      );
    }

    console.log(`[uats-patch] Fetched ${countyMap.size} counties from eTerra`);

    // 3. Load all UATs from DB
    const allUats = await prisma.gisUat.findMany({
      select: { siruta: true, name: true, county: true, workspacePk: true },
    });

    // Phase 1: instant fill for UATs that already have workspacePk
    const phase1Ops: Array<ReturnType<typeof prisma.gisUat.update>> = [];
    const needsCounty: Array<{ siruta: string; name: string }> = [];

    for (const uat of allUats) {
      if (uat.county) continue; // already has county

      if (uat.workspacePk && uat.workspacePk > 0) {
        const county = countyMap.get(uat.workspacePk);
        if (county) {
          phase1Ops.push(
            prisma.gisUat.update({
              where: { siruta: uat.siruta },
              data: { county },
            }),
          );
          continue;
        }
      }
      needsCounty.push({ siruta: uat.siruta, name: uat.name });
    }

    // Execute phase 1 in batches
    let phase1Updated = 0;
    for (let i = 0; i < phase1Ops.length; i += 100) {
      const batch = phase1Ops.slice(i, i + 100);
      await prisma.$transaction(batch);
      phase1Updated += batch.length;
    }

    console.log(
      `[uats-patch] Phase 1: ${phase1Updated} updated via workspacePk. ` +
        `${needsCounty.length} remaining.`,
    );

    // Phase 2: enumerate UATs per county from nomenclature, match by code or name
    // Build lookups
    const nameToSirutas = new Map<string, string[]>();
    const sirutaSet = new Set<string>();
    for (const u of needsCounty) {
      sirutaSet.add(u.siruta);
      const key = normalizeName(u.name);
      const arr = nameToSirutas.get(key);
      if (arr) arr.push(u.siruta);
      else nameToSirutas.set(key, [u.siruta]);
    }

    let phase2Updated = 0;
    let codeMatches = 0;
    let nameMatches = 0;
    const matchedSirutas = new Set<string>();
    let loggedSample = false;
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    let sampleCounty: any = null;
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    let sampleUat: any = null;
    let totalEterraUats = 0;

    for (const [countyPk, countyName] of countyMap) {
      if (matchedSirutas.size >= needsCounty.length) break;

      try {
        const rawUats = await client.fetchAdminUnitsByCounty(countyPk);
        const uats = unwrapArray(rawUats);

        totalEterraUats += uats.length;

        // Log first county's first UAT for debugging
        if (!loggedSample && uats.length > 0) {
          sampleUat = uats[0];
          sampleCounty = { pk: countyPk, name: countyName, uatCount: uats.length };
          console.log(
            `[uats-patch] Sample UAT from ${countyName} (${uats.length} UATs):`,
            JSON.stringify(uats[0]).slice(0, 500),
          );
          console.log(
            `[uats-patch] Sample UAT keys:`,
            Object.keys(uats[0] ?? {}),
          );
          loggedSample = true;
        }

        for (const uat of uats) {
          // Strategy A: match by code (might be SIRUTA)
          const code = extractCode(uat);
          if (code && sirutaSet.has(code) && !matchedSirutas.has(code)) {
            matchedSirutas.add(code);
            await prisma.gisUat.update({
              where: { siruta: code },
              data: { county: countyName, workspacePk: countyPk },
            });
            phase2Updated++;
            codeMatches++;
            continue;
          }

          // Strategy B: match by normalized name
          const eterraName = extractName(uat);
          if (!eterraName) continue;

          const key = normalizeName(eterraName);
          const sirutas = nameToSirutas.get(key);
          if (!sirutas || sirutas.length === 0) continue;

          const siruta = sirutas.find((s) => !matchedSirutas.has(s));
          if (!siruta) continue;

          matchedSirutas.add(siruta);
          await prisma.gisUat.update({
            where: { siruta },
            data: { county: countyName, workspacePk: countyPk },
          });
          phase2Updated++;
          nameMatches++;

          if (sirutas.every((s) => matchedSirutas.has(s))) {
            nameToSirutas.delete(key);
          }
        }
      } catch (err) {
        console.warn(
          `[uats-patch] Failed to fetch UATs for county ${countyName} (pk=${countyPk}):`,
          err instanceof Error ? err.message : err,
        );
      }
    }

    const totalUpdated = phase1Updated + phase2Updated;
    const unmatched = needsCounty.length - phase2Updated;
    console.log(
      `[uats-patch] Phase 2: ${phase2Updated} (${codeMatches} by code, ${nameMatches} by name). ` +
        `Total: ${totalUpdated}. Unmatched: ${unmatched}.`,
    );

    return NextResponse.json({
      updated: totalUpdated,
      phase1: phase1Updated,
      phase2: phase2Updated,
      codeMatches,
      nameMatches,
      totalCounties: countyMap.size,
      totalEterraUats,
      unmatched,
      // Include debug samples so we can see what eTerra returns
      debug: {
        sampleCounty,
        sampleUatKeys: sampleUat ? Object.keys(sampleUat) : null,
        sampleUat: sampleUat
          ? JSON.parse(JSON.stringify(sampleUat).slice(0, 500))
          : null,
        sampleCountyRaw: counties[0]
          ? {
              keys: Object.keys(counties[0]),
              nomenPk: counties[0].nomenPk,
              name: counties[0].name,
            }
          : null,
      },
    });
  } catch (error) {
    const message = error instanceof Error ? error.message : "Eroare server";
    console.error("[uats-patch] Error:", message);
    return NextResponse.json({ error: message }, { status: 500 });
  }
}
