import { useState, useRef, useCallback, useEffect } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Progress } from "@/components/ui/progress";
import { Upload, FileText, Database, CheckCircle2, AlertCircle, Loader2, Scissors, SkipForward, Globe, Terminal, RefreshCw } from "lucide-react";
import { supabase } from "@/integrations/supabase/client";
import { unzipSync } from "fflate";

const CHUNK_SIZE = 5000; // rows per API call — reduced to avoid DB statement timeouts
const UI_UPDATE_INTERVAL = 25000; // only update UI every N rows to reduce memory pressure
const CHECKPOINT_INTERVAL = 50000; // save checkpoint every N rows
const GC_PAUSE_INTERVAL = 100000; // pause to let browser GC every N rows

interface ImportState {
  status: "idle" | "parsing" | "uploading" | "done" | "error" | "resume_prompt";
  fileName: string;
  totalRows: number;
  processedRows: number;
  insertedRows: number;
  errorRows: number;
  errorMessages: string[];
  resumeFrom: number; // rows to skip when resuming
  jobId: string | null; // DB import_jobs id for URL imports
}

const initialState: ImportState = {
  status: "idle",
  fileName: "",
  totalRows: 0,
  processedRows: 0,
  insertedRows: 0,
  errorRows: 0,
  errorMessages: [],
  resumeFrom: 0,
  jobId: null,
};

function getCheckpointKey(type: string) {
  return `import_checkpoint_${type}`;
}

interface Checkpoint {
  fileName: string;
  fileSize: number;
  processedRows: number;
  insertedRows: number;
  timestamp: number;
}

function saveCheckpoint(type: string, cp: Checkpoint) {
  try { localStorage.setItem(getCheckpointKey(type), JSON.stringify(cp)); } catch {}
}

function loadCheckpoint(type: string): Checkpoint | null {
  try {
    const raw = localStorage.getItem(getCheckpointKey(type));
    return raw ? JSON.parse(raw) : null;
  } catch { return null; }
}

function clearCheckpoint(type: string) {
  try { localStorage.removeItem(getCheckpointKey(type)); } catch {}
}

function normalizeHeader(name: string): string {
  return name
    .toLowerCase()
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "") // Remove accents
    .replace(/[_\s]+/g, "_")
    .replace(/[^a-z0-9_]/g, "")
    .trim();
}

// Map of normalized header names to expected DB column names for fiber
const FIBER_HEADER_MAP: Record<string, string> = {
  id_placemark: "id_placemark",
  idplacemark: "id_placemark",
  id_appart: "id_appart",
  idappart: "id_appart",
  uuidadresse: "uuidadresse",
  idgouvqc: "uuidadresse",
  id_gouv_qc: "uuidadresse",
  nom: "nom",
  civique: "civique",
  appartement: "appartement",
  appt: "appartement",
  apt: "appartement",
  rue: "rue",
  ville: "ville",
  code_postal: "code_postal",
  codepostal: "code_postal",
  cp: "code_postal",
  lien_googlemap: "lien_googlemap",
  liengooglemap: "lien_googlemap",
  googlemap: "lien_googlemap",
  zone_tarifaire: "zone_tarifaire",
  zonetarifaire: "zone_tarifaire",
  zone: "zone_tarifaire",
  max_speed: "max_speed",
  maxspeed: "max_speed",
  vitesse: "max_speed",
  vitesse_max: "max_speed",
  commentaire: "commentaire",
  commentaires: "commentaire",
  comment: "commentaire",
  lien_map_targo: "_skip",
  lienmaptargo: "_skip",
};

function parseLine(line: string, delimiter: string): string[] {
  if (delimiter === "\t" || delimiter === ";") {
    return line.split(delimiter).map((v) => v.trim().replace(/^"|"$/g, ""));
  }
  // CSV with potential quoted fields
  const values: string[] = [];
  let current = "";
  let inQuotes = false;
  for (let i = 0; i < line.length; i++) {
    const char = line[i];
    if (char === '"') {
      if (inQuotes && i + 1 < line.length && line[i + 1] === '"') {
        current += '"';
        i++;
      } else {
        inQuotes = !inQuotes;
      }
    } else if (char === delimiter && !inQuotes) {
      values.push(current.trim());
      current = "";
    } else {
      current += char;
    }
  }
  values.push(current.trim());
  return values;
}

function ImportCard({
  type,
  label,
  description,
}: {
  type: "addresses" | "fiber";
  label: string;
  description: string;
}) {
  const [state, setState] = useState<ImportState>(initialState);
  const [urlInput, setUrlInput] = useState("https://diffusion.mern.gouv.qc.ca/diffusion/RGQ/Vectoriel/Theme/Local/RQA/CSV/RQA_CSV.zip");
  const [urlMode, setUrlMode] = useState(false);
  const [skipRowsInput, setSkipRowsInput] = useState("");
  const [importLogs, setImportLogs] = useState<string[]>([]);
  const [showLogs, setShowLogs] = useState(false);
  const [pendingJob, setPendingJob] = useState<any>(null);
  const abortRef = useRef(false);
  const fileInputRef = useRef<HTMLInputElement>(null);
  const startTimeRef = useRef<number>(0);
  const logEndRef = useRef<HTMLDivElement>(null);
  const autoResumeTriggered = useRef(false);

  const pendingFileRef = useRef<File | null>(null);

  // Check for existing checkpoint on mount
  const checkpoint = loadCheckpoint(type);

  const addLog = (msg: string) => {
    setImportLogs(prev => [...prev, msg]);
    setTimeout(() => logEndRef.current?.scrollIntoView({ behavior: "smooth" }), 50);
  };

  const reset = (keepCheckpoint = false) => {
    abortRef.current = true;
    if (!keepCheckpoint) clearCheckpoint(type);
    pendingFileRef.current = null;
    setPendingJob(null);
    setUrlMode(false);
    setImportLogs([]);
    setState(initialState);
  };

  // Helper to update job in DB
  const updateJob = async (jobId: string, updates: Record<string, any>) => {
    try {
      await supabase.from("import_jobs").update({ ...updates, updated_at: new Date().toISOString() }).eq("id", jobId);
    } catch {}
  };

  // Check for incomplete DB jobs on mount (URL imports only)
  useEffect(() => {
    if (autoResumeTriggered.current) return;
    autoResumeTriggered.current = true;
    
    const checkPendingJobs = async () => {
      const { data: session } = await supabase.auth.getSession();
      if (!session?.session?.user) return;
      
      const { data: jobs } = await supabase
        .from("import_jobs")
        .select("*")
        .eq("type", type)
        .in("status", ["running", "paused"])
        .order("updated_at", { ascending: false })
        .limit(1);
      
      if (jobs && jobs.length > 0) {
        const job = jobs[0];
        setPendingJob(job);
        // Restore logs from DB
        if (job.logs && job.logs.length > 0) {
          setImportLogs(job.logs);
          setShowLogs(true);
        }
      }
    };
    checkPendingJobs();
  }, [type]);

  // URL-based import: loops calling edge function with DB checkpointing
  const processUrl = useCallback(
    async (url: string, resumeJobId?: string, resumeSkip?: number, resumeInserted?: number) => {
      abortRef.current = false;
      startTimeRef.current = Date.now();
      if (!resumeJobId) {
        setImportLogs([]);
      }
      setShowLogs(true);
      setPendingJob(null);
      addLog(`🚀 Démarrage import ${type} depuis URL${resumeSkip ? ` (reprise à ${resumeSkip} lignes)` : ""}...`);
      
      // Get user id
      const { data: sessionData } = await supabase.auth.getSession();
      const userId = sessionData?.session?.user?.id;
      if (!userId) {
        addLog("❌ Non authentifié");
        setState(s => ({ ...s, status: "error", errorMessages: ["Non authentifié"] }));
        return;
      }

      // Create or reuse DB job
      let jobId = resumeJobId;
      if (!jobId) {
        const { data: newJob, error: jobErr } = await supabase
          .from("import_jobs")
          .insert({ type, source_url: url, status: "running", user_id: userId })
          .select("id")
          .single();
        if (jobErr || !newJob) {
          addLog(`❌ Erreur création job: ${jobErr?.message}`);
          setState(s => ({ ...s, status: "error", errorMessages: [jobErr?.message || "Erreur"] }));
          return;
        }
        jobId = newJob.id;
      } else {
        await updateJob(jobId, { status: "running" });
      }

      setState({
        ...initialState,
        status: "uploading",
        fileName: url.split("/").pop() || "URL import",
        totalRows: 0,
        jobId,
        processedRows: resumeSkip || 0,
        insertedRows: resumeInserted || 0,
      });

      const isZip = url.toLowerCase().includes(".zip");
      let csvText: string | null = null;

      if (isZip) {
        // Download and decompress ZIP in the browser
        addLog(`📥 Téléchargement du fichier ZIP...`);
        try {
          const resp = await fetch(url);
          if (!resp.ok) {
            addLog(`❌ Erreur téléchargement: ${resp.status} ${resp.statusText}`);
            await updateJob(jobId!, { status: "error", error_messages: [`Erreur: ${resp.status}`] });
            setState((s) => ({ ...s, status: "error", errorMessages: [`Erreur: ${resp.status}`] }));
            return;
          }
          const zipData = new Uint8Array(await resp.arrayBuffer());
          addLog(`📦 ZIP téléchargé (${(zipData.length / 1024 / 1024).toFixed(1)} MB), décompression...`);

          const files = unzipSync(zipData);
          const csvEntry = Object.entries(files).find(([name]) =>
            name.toLowerCase().endsWith(".csv") || name.toLowerCase().endsWith(".tsv") || name.toLowerCase().endsWith(".txt")
          );
          if (!csvEntry) {
            addLog(`❌ Aucun fichier CSV trouvé dans le ZIP`);
            await updateJob(jobId!, { status: "error", error_messages: ["Aucun CSV dans le ZIP"] });
            setState((s) => ({ ...s, status: "error", errorMessages: ["Aucun CSV dans le ZIP"] }));
            return;
          }
          addLog(`📄 Fichier extrait: ${csvEntry[0]} (${(csvEntry[1].length / 1024 / 1024).toFixed(1)} MB)`);
          csvText = new TextDecoder("utf-8").decode(csvEntry[1]);
        } catch (err: any) {
          addLog(`💥 Erreur décompression: ${err.message}`);
          await updateJob(jobId!, { status: "error", error_messages: [err.message] });
          setState((s) => ({ ...s, status: "error", errorMessages: [err.message] }));
          return;
        }
      }

      // Now send CSV data in chunks to edge function
      let skipRows = resumeSkip || 0;
      let totalInserted = resumeInserted || 0;
      let totalErrors = 0;
      const allErrorMessages: string[] = [];
      const CSV_CHUNK_SIZE = 5 * 1024 * 1024; // 5 MB
      let chunkCount = 0;
      const DB_SAVE_INTERVAL = 3; // save to DB every N chunks

      while (!abortRef.current) {
        try {
          // Refresh session before every chunk to prevent expiry
          await supabase.auth.getSession();

          let body: any;
          if (csvText) {
            const chunk = csvText.slice(0, CSV_CHUNK_SIZE);
            body = { csv_text: chunk, type, skip_rows: skipRows };
          } else {
            body = { url, type, skip_rows: skipRows };
          }

          const { data, error } = await supabase.functions.invoke("import-csv-url", { body });

          if (error) {
            addLog(`❌ Erreur: ${error.message}`);
            await updateJob(jobId!, { status: "error", error_messages: [error.message], skip_rows: skipRows, total_inserted: totalInserted });
            setState((s) => ({ ...s, status: "error", errorMessages: [error.message] }));
            return;
          }

          if (data) {
            if (data.logs && Array.isArray(data.logs)) {
              data.logs.forEach((l: string) => addLog(l));
            }

            totalInserted += data.inserted || 0;
            totalErrors += data.errors || 0;
            skipRows = data.next_skip || skipRows;
            if (data.errorMessages) {
              allErrorMessages.push(...data.errorMessages);
            }
            chunkCount++;

            addLog(`📊 Chunk ${chunkCount} terminé: +${data.processed || 0} lignes, total inséré: ${totalInserted}, erreurs: ${totalErrors}`);

            setState((s) => ({
              ...s,
              processedRows: skipRows,
              insertedRows: totalInserted,
              errorRows: totalErrors,
              totalRows: data.done ? skipRows : Math.max(s.totalRows, skipRows + 50000),
              errorMessages: [...new Set(allErrorMessages)].slice(0, 5),
            }));

            // Save checkpoint to DB periodically
            if (chunkCount % DB_SAVE_INTERVAL === 0) {
              const recentLogs = importLogs.slice(-200); // keep last 200 log lines
              await updateJob(jobId!, {
                skip_rows: skipRows,
                total_inserted: totalInserted,
                total_errors: totalErrors,
                error_messages: [...new Set(allErrorMessages)].slice(0, 20),
                logs: recentLogs,
              });
            }

            if (data.done) {
              addLog(`✅ Import terminé! Total: ${skipRows} lignes, ${totalInserted} insérées, ${totalErrors} erreurs`);
              await updateJob(jobId!, { status: "done", skip_rows: skipRows, total_inserted: totalInserted, total_errors: totalErrors });
              setState((s) => ({
                ...s,
                status: "done",
                totalRows: skipRows,
                processedRows: skipRows,
              }));
              return;
            }

            // For CSV text mode, trim processed text
            if (csvText) {
              const lines = csvText.split("\n");
              const rowsToRemove = data.processed || 0;
              const linesToRemove = skipRows === rowsToRemove ? rowsToRemove + 1 : rowsToRemove;
              csvText = lines.slice(linesToRemove).join("\n");
              skipRows = 0;
              if (!csvText.trim()) {
                addLog(`✅ Import terminé! Total: ${totalInserted} insérées, ${totalErrors} erreurs`);
                await updateJob(jobId!, { status: "done", total_inserted: totalInserted, total_errors: totalErrors });
                setState((s) => ({ ...s, status: "done" }));
                return;
              }
            }

            await new Promise((r) => setTimeout(r, 500));
          }
        } catch (err: any) {
          addLog(`💥 Exception: ${err.message}`);
          // Save progress before dying so we can resume
          await updateJob(jobId!, { status: "paused", skip_rows: skipRows, total_inserted: totalInserted, total_errors: totalErrors, error_messages: [err.message] });
          setState((s) => ({ ...s, status: "error", errorMessages: [`${err.message} — L'import peut être repris automatiquement.`] }));
          return;
        }
      }

      // User cancelled — save progress
      if (abortRef.current && jobId) {
        await updateJob(jobId, { status: "paused", skip_rows: skipRows, total_inserted: totalInserted, total_errors: totalErrors });
      }
    },
    [type, importLogs]
  );

  const processFile = useCallback(
    async (file: File, skipRows = 0) => {
      abortRef.current = false;
      startTimeRef.current = Date.now();
      const alreadyInserted = skipRows > 0 ? (loadCheckpoint(type)?.insertedRows || 0) : 0;
      setState({
        ...initialState,
        status: "parsing",
        fileName: file.name,
        resumeFrom: skipRows,
      });

      // Estimate total rows from file size (rough: ~300 bytes per row for addresses CSV)
      const estimatedRows = Math.round(file.size / 300);
      setState((s) => ({ ...s, totalRows: estimatedRows, status: "uploading" }));

      const stream = file.stream();
      const reader = stream.getReader();
      const decoder = new TextDecoder("utf-8", { fatal: false });

      let headers: string[] = [];
      let delimiter = ",";
      let leftover = "";
      let isFirstChunk = true;
      let processedRows = 0;
      let insertedRows = alreadyInserted;
      let errorRows = 0;
      const errorMessages: string[] = [];
      let pendingRows: Record<string, string>[] = [];
      let totalRowsRead = 0; // total rows read from file (including skipped)

      const flushBatch = async (rows: Record<string, string>[]) => {
        if (rows.length === 0 || abortRef.current) return;
        try {
          // Refresh session token before each batch to prevent expiry during long imports
          await supabase.auth.getSession();
          const { data, error } = await supabase.functions.invoke("import-csv", {
            body: { type, rows },
          });
          if (error) {
            errorRows += rows.length;
            if (errorMessages.length < 5) errorMessages.push(error.message);
          } else if (data) {
            insertedRows += data.inserted || 0;
            errorRows += data.errors || 0;
            if (data.errorMessages) {
              errorMessages.push(...data.errorMessages.slice(0, 3));
            }
          }
        } catch (err: any) {
          errorRows += rows.length;
          if (errorMessages.length < 5) errorMessages.push(err.message);
        }
        // Throttle UI updates to reduce memory pressure from React re-renders
        if (processedRows % UI_UPDATE_INTERVAL < CHUNK_SIZE) {
          setState((s) => ({
            ...s,
            processedRows,
            insertedRows,
            errorRows,
            totalRows: Math.max(s.totalRows, processedRows + skipRows),
            errorMessages: [...new Set(errorMessages)].slice(0, 5),
          }));
        }
        // Save checkpoint less frequently
        if (processedRows % CHECKPOINT_INTERVAL < CHUNK_SIZE) {
          saveCheckpoint(type, {
            fileName: file.name,
            fileSize: file.size,
            processedRows: processedRows + skipRows,
            insertedRows,
            timestamp: Date.now(),
          });
        }
      };

      try {
        while (true) {
          if (abortRef.current) break;
          const { done, value } = await reader.read();
          const chunk = decoder.decode(value, { stream: !done });
          const text = leftover + chunk;
          const lines = text.split(/\r?\n/);
          
          // Keep last incomplete line for next iteration
          leftover = done ? "" : (lines.pop() || "");

          for (const line of lines) {
            const trimmed = line.trim();
            if (!trimmed) continue;

            if (isFirstChunk && headers.length === 0) {
              // Remove BOM
              const cleanLine = trimmed.charCodeAt(0) === 0xfeff ? trimmed.slice(1) : trimmed;
              // Detect delimiter
              const tabCount = (cleanLine.match(/\t/g) || []).length;
              const semiCount = (cleanLine.match(/;/g) || []).length;
              const commaCount = (cleanLine.match(/,/g) || []).length;
              delimiter = tabCount >= semiCount && tabCount >= commaCount ? "\t"
                         : semiCount >= commaCount ? ";" : ",";
              const candidateHeaders = parseLine(cleanLine, delimiter);
              
              // Skip title lines that have too few columns (e.g. "adresses postales")
              if (candidateHeaders.length < 3) {
                console.log(`[Import] Skipping title line: "${cleanLine}"`);
                continue;
              }
              
              headers = candidateHeaders;
              console.log(`[Import] Stream - Delimiter: "${delimiter === "\t" ? "TAB" : delimiter}", Headers (${headers.length}):`, headers.join(" | "));
              
              // For fiber imports, build a mapping from CSV header index to normalized DB column
              if (type === "fiber") {
                const headerMapping = headers.map((h) => {
                  const norm = normalizeHeader(h);
                  const mapped = FIBER_HEADER_MAP[norm];
                  return mapped ? `${h} → ${mapped}` : `${h} → ❌ UNMAPPED (${norm})`;
                });
                console.log(`[Import] Fiber header mapping:`, headerMapping.join(" | "));
              }
              
              isFirstChunk = false;
              continue;
            }

            const values = parseLine(trimmed, delimiter);
            const row: Record<string, string> = {};
            totalRowsRead++;

            // Skip rows that were already processed in a previous run
            if (totalRowsRead <= skipRows) {
              if (totalRowsRead % 100000 === 0) {
                setState((s) => ({
                  ...s,
                  status: "uploading",
                  processedRows: 0,
                  totalRows: Math.max(s.totalRows, estimatedRows),
                  errorMessages: [`⏩ Saut de ${totalRowsRead.toLocaleString("fr-CA")} lignes déjà importées...`],
                }));
              }
              continue;
            }

            if (type === "fiber") {
              // Use normalized mapping for fiber
              headers.forEach((h, idx) => {
                const norm = normalizeHeader(h);
                const dbCol = FIBER_HEADER_MAP[norm];
                if (dbCol && dbCol !== "_skip") {
                  row[dbCol] = values[idx] || "";
                }
              });
            } else {
              headers.forEach((h, idx) => {
                row[h] = values[idx] || "";
              });
            }
            pendingRows.push(row);
            processedRows++;

            if (pendingRows.length >= CHUNK_SIZE) {
              await flushBatch(pendingRows);
              pendingRows = [];
              // Periodically pause to let the browser GC reclaim memory
              if (processedRows % GC_PAUSE_INTERVAL < CHUNK_SIZE) {
                await new Promise((r) => setTimeout(r, 50));
              }
            }
          }

          if (done) break;
        }

        // Flush remaining rows
        if (pendingRows.length > 0) {
          await flushBatch(pendingRows);
        }
      } catch (err: any) {
        console.error("[Import] Stream error:", err);
        setState((s) => ({
          ...s,
          status: "error",
          errorMessages: [`Erreur de lecture: ${err.message}`],
        }));
        return;
      }

      if (!abortRef.current) {
        clearCheckpoint(type); // Import completed successfully
      }
      setState((s) => ({
        ...s,
        status: abortRef.current ? "idle" : "done",
        totalRows: processedRows + skipRows,
        processedRows,
        insertedRows,
        errorRows,
      }));
    },
    [type]
  );

  const progress =
    state.totalRows > 0
      ? Math.round((state.processedRows / state.totalRows) * 100)
      : 0;

  const formatNum = (n: number) => n.toLocaleString("fr-CA");

  const getStats = () => {
    if (state.processedRows === 0 || state.status !== "uploading") return { eta: "", speed: "" };
    const elapsed = (Date.now() - startTimeRef.current) / 1000;
    const rate = state.processedRows / elapsed;
    const remaining = state.totalRows - state.processedRows;
    const etaSec = remaining / rate;
    let eta = "";
    if (etaSec < 60) eta = `~${Math.round(etaSec)}s restant`;
    else if (etaSec < 3600) eta = `~${Math.round(etaSec / 60)} min restant`;
    else {
      const h = Math.floor(etaSec / 3600);
      const m = Math.round((etaSec % 3600) / 60);
      eta = `~${h}h${m.toString().padStart(2, "0")} restant`;
    }
    const speed = rate >= 1000
      ? `${(rate / 1000).toFixed(1)}k lignes/s`
      : `${Math.round(rate)} lignes/s`;
    return { eta, speed };
  };

  return (
    <div className="bg-card border border-border rounded-2xl p-6 space-y-4">
      <div className="flex items-start gap-4">
        <div className="w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center flex-shrink-0">
          {type === "addresses" ? (
            <Database className="w-6 h-6 text-primary" />
          ) : (
            <FileText className="w-6 h-6 text-primary" />
          )}
        </div>
        <div>
          <h3 className="font-semibold text-lg">{label}</h3>
          <p className="text-sm text-muted-foreground">{description}</p>
        </div>
      </div>

      <input
        ref={fileInputRef}
        type="file"
        accept=".csv,.tsv,.txt"
        className="hidden"
        onChange={(e) => {
          const file = e.target.files?.[0];
          if (file) {
            const cp = loadCheckpoint(type);
            if (cp && cp.fileName === file.name && cp.fileSize === file.size) {
              // Same file — offer to resume
              pendingFileRef.current = file;
              setState((s) => ({
                ...s,
                status: "resume_prompt",
                fileName: file.name,
                resumeFrom: cp.processedRows,
                insertedRows: cp.insertedRows,
              }));
            } else {
              clearCheckpoint(type);
              processFile(file, 0);
            }
          }
          e.target.value = "";
        }}
      />

      {state.status === "idle" && (
        <>
          {/* DB-based resume for URL imports */}
           {pendingJob && (
            <div className="bg-accent/50 rounded-lg p-3 text-sm space-y-2 border border-primary/20">
              <div className="flex items-center gap-2">
                <RefreshCw className="w-4 h-4 text-primary" />
                <span className="font-medium">Import URL interrompu détecté</span>
              </div>
              <p className="text-muted-foreground text-xs">
                {pendingJob.source_url?.split("/").pop()} — {(pendingJob.skip_rows || 0).toLocaleString("fr-CA")} lignes traitées
                ({(pendingJob.total_inserted || 0).toLocaleString("fr-CA")} insérées).
                Statut: <span className="font-medium">{pendingJob.status}</span>.
                Dernière mise à jour: {new Date(pendingJob.updated_at).toLocaleString("fr-CA")}.
              </p>
              {pendingJob.error_messages && pendingJob.error_messages.length > 0 && (
                <div className="bg-destructive/10 rounded p-2 text-xs text-destructive">
                  <span className="font-medium">Raison de l'interruption:</span>
                  <ul className="list-disc ml-4 mt-1">
                    {pendingJob.error_messages.map((msg: string, i: number) => (
                      <li key={i}>{msg}</li>
                    ))}
                  </ul>
                </div>
              )}
              {pendingJob.skip_rows === 0 && (!pendingJob.error_messages || pendingJob.error_messages.length === 0) && (
                <div className="bg-yellow-500/10 rounded p-2 text-xs text-yellow-700">
                  <span className="font-medium">Cause probable:</span> Le navigateur a manqué de mémoire lors du téléchargement ou de la décompression du ZIP (2.7 Go). 
                  Essayez depuis un ordinateur de bureau avec plus de RAM, ou utilisez un fichier CSV déjà décompressé.
                </div>
              )}
              {pendingJob.logs && pendingJob.logs.length > 0 && (
                <details className="text-xs">
                  <summary className="cursor-pointer text-muted-foreground hover:text-foreground">Voir les {pendingJob.logs.length} derniers logs</summary>
                  <div className="mt-1 max-h-32 overflow-y-auto bg-background/50 rounded p-2 font-mono text-[10px] space-y-0.5">
                    {pendingJob.logs.map((log: string, i: number) => (
                      <div key={i}>{log}</div>
                    ))}
                  </div>
                </details>
              )}
              <div className="flex gap-2">
                <Button size="sm" onClick={() => {
                  processUrl(pendingJob.source_url, pendingJob.id, pendingJob.skip_rows || 0, pendingJob.total_inserted || 0);
                }}>
                  <RefreshCw className="w-3 h-3 mr-1" />
                  Reprendre l'import
                </Button>
                <Button variant="ghost" size="sm" onClick={async () => {
                  await supabase.from("import_jobs").update({ status: "done" }).eq("id", pendingJob.id);
                  setPendingJob(null);
                }}>
                  Ignorer
                </Button>
              </div>
            </div>
          )}
          {checkpoint && (
            <div className="bg-accent/50 rounded-lg p-3 text-sm space-y-2">
              <div className="flex items-center gap-2">
                <SkipForward className="w-4 h-4 text-primary" />
                <span className="font-medium">Import fichier interrompu détecté</span>
              </div>
              <p className="text-muted-foreground text-xs">
                {checkpoint.fileName} — {checkpoint.processedRows.toLocaleString("fr-CA")} lignes déjà traitées
                ({checkpoint.insertedRows.toLocaleString("fr-CA")} insérées).
                Sélectionnez le même fichier pour reprendre.
              </p>
              <Button variant="ghost" size="sm" onClick={() => { clearCheckpoint(type); setState({...initialState}); }}>
                Ignorer et recommencer
              </Button>
            </div>
          )}
          <div className="flex gap-2">
            <Button
              onClick={() => fileInputRef.current?.click()}
              className="flex-1"
              variant="outline"
              size="lg"
            >
              <Upload className="w-5 h-5 mr-2" />
              Fichier CSV
            </Button>
            <Button
              onClick={() => setUrlMode(!urlMode)}
              className="flex-1"
              variant={urlMode ? "default" : "outline"}
              size="lg"
            >
              <Globe className="w-5 h-5 mr-2" />
              Lien URL
            </Button>
          </div>
          {urlMode && (
            <div className="space-y-2">
              <Input
                placeholder="https://exemple.com/fichier.csv ou .zip"
                value={urlInput}
                onChange={(e) => setUrlInput(e.target.value)}
              />
              <p className="text-xs text-muted-foreground">
                Supporte les fichiers CSV et ZIP. Le mode autonome décompresse et traite le fichier entièrement côté serveur — idéal depuis un téléphone.
              </p>
              <div className="space-y-2">
                <div className="flex gap-2 items-center">
                  <label className="text-xs text-muted-foreground whitespace-nowrap">Lignes à sauter :</label>
                  <Input
                    type="number"
                    min={0}
                    placeholder="0"
                    value={skipRowsInput}
                    onChange={(e) => setSkipRowsInput(e.target.value)}
                    className="w-32"
                  />
                  <span className="text-xs text-muted-foreground">({Number(skipRowsInput || 0).toLocaleString()})</span>
                </div>
                <div className="flex gap-2">
                  <Button
                    onClick={() => {
                      if (urlInput.trim()) processUrl(urlInput.trim());
                    }}
                    disabled={!urlInput.trim()}
                    className="flex-1"
                    variant="outline"
                  >
                    <Globe className="w-4 h-4 mr-2" />
                    Import navigateur
                  </Button>
                  <Button
                    onClick={async () => {
                      if (!urlInput.trim()) return;
                      const { data: sessionData } = await supabase.auth.getSession();
                      const userId = sessionData?.session?.user?.id;
                      if (!userId) { addLog("❌ Non authentifié"); return; }
                      const skipVal = parseInt(skipRowsInput || "0") || 0;
                      const { data: newJob, error: jobErr } = await supabase
                        .from("import_jobs")
                        .insert({ type, source_url: urlInput.trim(), status: "queued", user_id: userId, skip_rows: skipVal, total_inserted: skipVal })
                        .select("id")
                        .single();
                      if (jobErr || !newJob) {
                        addLog(`❌ Erreur: ${jobErr?.message}`);
                        return;
                      }
                      addLog(`🤖 Job autonome créé (${newJob.id.slice(0, 8)}…). Skip=${skipVal.toLocaleString()} lignes. Le serveur traitera le fichier automatiquement toutes les 2 minutes.`);
                      setShowLogs(true);
                      setPendingJob({ ...newJob, type, source_url: urlInput.trim(), status: "queued", skip_rows: skipVal, total_inserted: skipVal });
                    }}
                    disabled={!urlInput.trim()}
                    className="flex-1"
                  >
                    <Database className="w-4 h-4 mr-2" />
                    🤖 Mode autonome
                  </Button>
                </div>
              </div>
            </div>
          )}
        </>
      )}

      {state.status === "resume_prompt" && (
        <div className="space-y-3">
          <div className="flex items-center gap-2 text-primary">
            <SkipForward className="w-5 h-5" />
            <span className="font-medium">Reprendre l'import ?</span>
          </div>
          <p className="text-sm text-muted-foreground">
            {state.fileName} — {formatNum(state.resumeFrom)} lignes déjà importées
            ({formatNum(state.insertedRows)} insérées). Reprendre à partir de la ligne {formatNum(state.resumeFrom + 1)} ?
          </p>
          <div className="flex gap-2">
            <Button onClick={() => {
              const file = pendingFileRef.current;
              if (file) processFile(file, state.resumeFrom);
            }}>
              <SkipForward className="w-4 h-4 mr-2" />
              Reprendre
            </Button>
            <Button variant="outline" onClick={() => {
              clearCheckpoint(type);
              const file = pendingFileRef.current;
              if (file) processFile(file, 0);
            }}>
              Recommencer à zéro
            </Button>
            <Button variant="ghost" onClick={() => reset()}>
              Annuler
            </Button>
          </div>
        </div>
      )}

      {(state.status === "parsing" || state.status === "uploading") && (
        <div className="space-y-3">
          <div className="flex items-center justify-between text-sm">
            <span className="flex items-center gap-2">
              <Loader2 className="w-4 h-4 animate-spin" />
              {state.status === "parsing" ? "Lecture du fichier..." : 
               state.resumeFrom > 0 ? `Import en cours (reprise après ${formatNum(state.resumeFrom)} lignes)...` : "Import en cours..."}
            </span>
            <span className="font-mono text-muted-foreground">
              {formatNum(state.processedRows)} / {formatNum(state.totalRows)}
            </span>
          </div>
          <Progress value={progress} className="h-3" />
          <div className="flex justify-between text-xs text-muted-foreground">
            <span>{progress}% — {getStats().eta}</span>
            <span>
              ⚡ {getStats().speed} · ✓ {formatNum(state.insertedRows)} insérées · ✗ {formatNum(state.errorRows)} erreurs
            </span>
          </div>
          <Button variant="destructive" size="sm" onClick={() => reset(true)}>
            Annuler (garder checkpoint)
          </Button>
        </div>
      )}

      {state.status === "done" && (
        <div className="space-y-3">
          <div className="flex items-center gap-2 text-primary">
            <CheckCircle2 className="w-5 h-5" />
            <span className="font-medium">Import terminé!</span>
          </div>
          <div className="text-sm space-y-1">
            <p>Fichier : {state.fileName}</p>
            <p>Total : {formatNum(state.totalRows)} lignes</p>
            <p>Insérées : {formatNum(state.insertedRows)}</p>
            {state.errorRows > 0 && (
              <p className="text-destructive">Erreurs : {formatNum(state.errorRows)}</p>
            )}
          </div>
          {state.errorMessages.length > 0 && (
            <div className="bg-destructive/10 rounded-lg p-3 text-xs space-y-1">
              {state.errorMessages.map((msg, i) => (
                <p key={i} className="text-destructive">{msg}</p>
              ))}
            </div>
          )}
          <Button variant="outline" onClick={() => reset()}>
            Nouvel import
          </Button>
        </div>
      )}

      {state.status === "error" && (
        <div className="space-y-3">
          <div className="flex items-center gap-2 text-destructive">
            <AlertCircle className="w-5 h-5" />
            <span className="font-medium">Erreur</span>
          </div>
          {state.errorMessages.map((msg, i) => (
            <p key={i} className="text-sm text-destructive">{msg}</p>
          ))}
          <div className="flex gap-2">
            <Button variant="outline" onClick={() => { reset(true); }}>
              Sélectionner le fichier pour reprendre
            </Button>
            <Button variant="ghost" onClick={() => reset()}>
              Abandonner
            </Button>
          </div>
        </div>
      )}

      {/* Log Panel */}
      {importLogs.length > 0 && (
        <div className="space-y-2">
          <Button
            variant="ghost"
            size="sm"
            onClick={() => setShowLogs(!showLogs)}
            className="flex items-center gap-2 text-xs"
          >
            <Terminal className="w-4 h-4" />
            {showLogs ? "Masquer" : "Afficher"} les logs ({importLogs.length})
          </Button>
          {showLogs && (
            <div className="bg-muted/50 border border-border rounded-lg p-3 max-h-64 overflow-y-auto font-mono text-xs space-y-0.5">
              {importLogs.map((line, i) => (
                <div key={i} className={line.includes("❌") || line.includes("💥") ? "text-destructive" : line.includes("✅") ? "text-primary" : "text-muted-foreground"}>
                  {line}
                </div>
              ))}
              <div ref={logEndRef} />
            </div>
          )}
        </div>
      )}
    </div>
  );
}

function CsvSplitter() {
  const [splitting, setSplitting] = useState(false);
  const [progress, setProgress] = useState({ message: "", current: 0, total: 0, files: 0 });
  const fileInputRef = useRef<HTMLInputElement>(null);
  const abortRef = useRef(false);
  const SPLIT_ROWS = 500_000;

  const splitFile = async (file: File) => {
    abortRef.current = false;
    setSplitting(true);
    setProgress({ message: "Lecture en streaming...", current: 0, total: 0, files: 0 });

    const stream = file.stream();
    const reader = stream.getReader();
    const decoder = new TextDecoder("utf-8", { fatal: false });

    let header = "";
    let leftover = "";
    let currentLines: string[] = [];
    let totalRows = 0;
    let fileCount = 0;
    const baseName = file.name.replace(/\.[^.]+$/, "");

    const flushToFile = (lines: string[]) => {
      fileCount++;
      const content = [header, ...lines].join("\n");
      const blob = new Blob([content], { type: "text/csv" });
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = url;
      a.download = `${baseName}_part${fileCount}.csv`;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
    };

    try {
      while (true) {
        if (abortRef.current) break;
        const { done, value } = await reader.read();
        const chunk = decoder.decode(value, { stream: !done });
        const text = leftover + chunk;
        const lines = text.split(/\r?\n/);
        leftover = done ? "" : (lines.pop() || "");

        for (const line of lines) {
          const trimmed = line.trim();
          if (!trimmed) continue;

          if (!header) {
            // Remove BOM
            header = trimmed.charCodeAt(0) === 0xfeff ? trimmed.slice(1) : trimmed;
            continue;
          }

          currentLines.push(trimmed);
          totalRows++;

          if (currentLines.length >= SPLIT_ROWS) {
            flushToFile(currentLines);
            currentLines = [];
            setProgress({
              message: `Fichier ${fileCount} créé...`,
              current: totalRows,
              total: Math.max(totalRows, Math.round(file.size / 300)),
              files: fileCount,
            });
            // Small delay to let browser process the download
            await new Promise((r) => setTimeout(r, 300));
          }
        }

        if (done) break;
      }

      // Flush remaining
      if (currentLines.length > 0 && !abortRef.current) {
        flushToFile(currentLines);
      }

      setProgress({
        message: `✅ ${fileCount} fichiers créés (${totalRows.toLocaleString("fr-CA")} lignes total)`,
        current: totalRows,
        total: totalRows,
        files: fileCount,
      });
    } catch (err: any) {
      setProgress({
        message: `❌ Erreur: ${err.message}`,
        current: totalRows,
        total: totalRows,
        files: fileCount,
      });
    }

    setSplitting(false);
  };

  const pct = progress.total > 0 ? Math.round((progress.current / progress.total) * 100) : 0;

  return (
    <div className="bg-card border border-border rounded-2xl p-6 space-y-4">
      <div className="flex items-start gap-4">
        <div className="w-12 h-12 rounded-xl bg-accent/50 flex items-center justify-center flex-shrink-0">
          <Scissors className="w-6 h-6 text-muted-foreground" />
        </div>
        <div>
          <h3 className="font-semibold text-lg">Découper un CSV</h3>
          <p className="text-sm text-muted-foreground">
            Sépare un gros fichier CSV en plusieurs fichiers de {(SPLIT_ROWS / 1000).toFixed(0)}k lignes
            pour éviter les timeouts lors de l'import. Utilise le streaming (pas de limite de taille).
          </p>
        </div>
      </div>

      <input
        ref={fileInputRef}
        type="file"
        accept=".csv,.tsv,.txt"
        className="hidden"
        onChange={(e) => {
          const file = e.target.files?.[0];
          if (file) splitFile(file);
          e.target.value = "";
        }}
      />

      {!splitting && !progress.message && (
        <Button onClick={() => fileInputRef.current?.click()} className="w-full" variant="outline" size="lg">
          <Scissors className="w-5 h-5 mr-2" />
          Sélectionner un CSV à découper
        </Button>
      )}

      {splitting && (
        <div className="space-y-3">
          <div className="flex items-center gap-2 text-sm text-muted-foreground">
            <Loader2 className="w-4 h-4 animate-spin" />
            {progress.message}
          </div>
          <Progress value={pct} className="h-3" />
          <div className="flex justify-between text-xs text-muted-foreground">
            <span>{progress.current.toLocaleString("fr-CA")} lignes lues</span>
            <span>{progress.files} fichiers créés</span>
          </div>
          <Button variant="destructive" size="sm" onClick={() => { abortRef.current = true; }}>
            Annuler
          </Button>
        </div>
      )}

      {!splitting && progress.message && (
        <div className="space-y-3">
          <p className="text-sm text-primary font-medium">{progress.message}</p>
          <p className="text-xs text-muted-foreground">
            Importez ensuite chaque partie via les cartes ci-dessus.
          </p>
          <Button variant="outline" size="sm" onClick={() => setProgress({ message: "", current: 0, total: 0, files: 0 })}>
            Découper un autre fichier
          </Button>
        </div>
      )}
    </div>
  );
}

const AdminImport = () => {
  return (
    <div className="min-h-screen bg-background">
      <div className="container max-w-3xl py-12 space-y-8">
        <div>
          <h1 className="font-display text-3xl font-bold">Import de données</h1>
          <p className="text-muted-foreground mt-2">
            Importez les fichiers CSV d'adresses CTOP et de disponibilité fibre. 
            Les fichiers sont traités par lots de {CHUNK_SIZE.toLocaleString("fr-CA")} lignes.
          </p>
          <div className="mt-3 p-3 bg-accent/50 rounded-lg text-sm text-muted-foreground">
            <strong>Important :</strong> Importez d'abord les adresses, puis la fibre (dépendance FK).
            Supporte CSV (virgule) et TSV (tabulation). Les doublons sont mis à jour automatiquement (upsert).
          </div>
        </div>

        <div className="space-y-6">
          <ImportCard
            type="addresses"
            label="1. Adresses CTOP"
            description="Fichier CSV/TSV des adresses du Québec (identifiant_unique_adresse, adresse_formatee, etc.)"
          />
          <ImportCard
            type="fiber"
            label="2. Disponibilité fibre"
            description="Fichier CSV/TSV de couverture fibre (uuidadresse, zone_tarifaire, max_speed, etc.)"
          />
        </div>

        <div className="border-t border-border pt-6">
          <h2 className="text-xl font-semibold mb-4">Outils</h2>
          <CsvSplitter />
        </div>
      </div>
    </div>
  );
};

export default AdminImport;
