import React, { useState, useEffect, useRef } from "react";
import { toast } from "react-toastify";
import { DataTable } from "primereact/datatable";
import { Column } from "primereact/column";
import { Dropdown } from "primereact/dropdown";
import { Button } from "@mui/joy";
import { Dialog } from "primereact/dialog";
import { authFetch } from "@/utils/authFetch";
import { confirmDialog, ConfirmDialog } from "primereact/confirmdialog";
import BreadcrumbNav from "./BreadcrumbNav";
import { API_URL } from "@/config";
import "./RequestManagement.css";

interface TrackInfo {
  title?: string;
  artist?: string;
  status?: string;
  error?: string;
  filename?: string;
  [key: string]: unknown;
}

interface RequestJob {
  id: string | number;
  target: string;
  tracks: number;
  quality: string;
  status: string;
  progress: number | string | null;
  type?: string;
  tarball?: string;
  created_at?: string;
  updated_at?: string;
  track_list?: TrackInfo[];
  [key: string]: unknown;
}

const STATUS_OPTIONS = ["Queued", "Started", "Compressing", "Finished", "Failed"];
const TAR_BASE_URL = "https://kr.codey.horse"; // configurable prefix

export default function RequestManagement() {
  const [requests, setRequests] = useState<RequestJob[]>([]);
  const [filterType, setFilterType] = useState<string | null>(null);
  const [filterStatus, setFilterStatus] = useState<string | null>(null);
  const [filteredRequests, setFilteredRequests] = useState<RequestJob[]>([]);
  const [selectedRequest, setSelectedRequest] = useState<RequestJob | null>(null);
  const [isDialogVisible, setIsDialogVisible] = useState(false);
  const [isLoading, setIsLoading] = useState(true);
  const pollingRef = useRef<ReturnType<typeof setInterval> | null>(null);
  const pollingDetailRef = useRef<ReturnType<typeof setInterval> | null>(null);
  // Track finalizing job polls to actively refresh job status when progress hits 100% but status hasn't updated yet
  const finalizingPollsRef = useRef<Record<string | number, ReturnType<typeof setInterval> | null>>({});


  const resolveTarballPath = (job: RequestJob) => job.tarball;

  const tarballUrl = (absPath: string | undefined, quality: string, jobType?: string) => {
    if (!absPath) return null;
    const filename = absPath.split("/").pop(); // get "SOMETHING.tar.gz"
    // If the backend already stores a fully qualified URL, return as-is
    if (/^https?:\/\//i.test(absPath)) return absPath;
    
    const isVideo = jobType === "video" || absPath.includes("/videos/");

    // Check if path is /storage/music/TRIP
    if (absPath.includes("/storage/music/TRIP/")) {
      return isVideo
        ? `https://_music.codey.horse/TRIP/videos/${filename}`
        : `https://_music.codey.horse/TRIP/${filename}`;
    }
    
    // Otherwise, assume /storage/music2/completed/{quality} format
    return `${TAR_BASE_URL}/${quality}/${filename}`;
  };

  const fetchJobs = async (showLoading = true) => {
    try {
      if (showLoading) setIsLoading(true);
      const res = await authFetch(`${API_URL}/trip/jobs/list`);
      if (!res.ok) throw new Error("Failed to fetch jobs");
      const data = await res.json() as { jobs?: RequestJob[] };
      setRequests(Array.isArray(data.jobs) ? data.jobs : []);
    } catch (err) {
      console.error(err);
      if (!toast.isActive('fetch-fail-toast')) {
        toast.error("Failed to fetch jobs list", {
          toastId: 'fetch-fail-toast',
        });
      }
    } finally {
      setIsLoading(false);
    }
  };

  const fetchJobDetail = async (jobId: string | number): Promise<RequestJob | null> => {
    try {
      const res = await authFetch(`${API_URL}/trip/job/${jobId}`);
      if (!res.ok) throw new Error("Failed to fetch job details");
      return await res.json() as RequestJob;
    } catch (err) {
      console.error(err);
      if (!toast.isActive('fetch-job-fail-toast')) {
        toast.error("Failed to fetch job details",
          {
            toastId: "fetch-job-fail-toast",
          });
      }
      return null;
    }
  };

  // Initial load shows the skeleton; subsequent polling should not
  useEffect(() => { fetchJobs(true); }, []);
  useEffect(() => {
    if (isDialogVisible && selectedRequest) {
      // Start polling
      const poll = async () => {
        const updated = await fetchJobDetail(selectedRequest.id);
        if (updated) setSelectedRequest(updated);
      };

      pollingDetailRef.current = setInterval(poll, 1500);

      return () => {
        if (pollingDetailRef.current) {
          clearInterval(pollingDetailRef.current);
          pollingDetailRef.current = null;
        }
      };
    }
  }, [isDialogVisible, selectedRequest?.id]);
  useEffect(() => {
    const hasActive = requests.some((j) => ["Queued", "Started", "Compressing"].includes(j.status));
    if (hasActive && !pollingRef.current) pollingRef.current = setInterval(() => fetchJobs(false), 1500);
    else if (!hasActive && pollingRef.current) {
      clearInterval(pollingRef.current);
      pollingRef.current = null;
    }
    return () => { if (pollingRef.current) clearInterval(pollingRef.current); pollingRef.current = null; };
  }, [requests]);

  useEffect(() => {
    const filtered = requests.filter((r) => {
      const typeMatch = !filterType || r.type === filterType;
      const statusMatch = filterStatus === "all" || filterStatus === null || r.status === filterStatus;
      return typeMatch && statusMatch;
    });
    setFilteredRequests(filtered);
  }, [filterType, filterStatus, requests]);


  const getStatusColorClass = (status: string) => {
    switch (status) {
      case "Queued": return "bg-yellow-700 text-white";
      case "Started": return "bg-blue-700 text-white";
      case "Compressing": return "bg-orange-700 text-white";
      case "Finished": return "bg-green-700 text-white";
      case "Failed": return "bg-red-700 text-white";
      default: return "bg-gray-700 text-white";
    }
  };

  const getQualityColorClass = (quality: string) => {
    switch (quality) {
      case "FLAC": return "bg-green-700 text-white";
      case "Lossy": return "bg-yellow-700 text-white";
      default: return "bg-gray-700 text-white";
    }
  };


  const statusBodyTemplate = (rowData: RequestJob) => (
    <span className={`inline-flex items-center justify-center min-w-[90px] px-3 py-1 rounded-full font-semibold text-xs ${getStatusColorClass(rowData.status)}`}>
      {rowData.status}
    </span>
  );

  const qualityBodyTemplate = (rowData: RequestJob) => (
    <span className={`inline-flex items-center justify-center min-w-[50px] px-3 py-1 rounded-full font-semibold text-xs ${getQualityColorClass(rowData.quality)}`}>
      {rowData.quality}
    </span>
  );


  const safeText = (val: unknown) => (val === 0 ? "0" : val || "—");
  const textWithEllipsis = (val: string | undefined | null, width = "12rem") => (
    <span className="truncate block" style={{ maxWidth: width }} title={val || ""}>{val || "—"}</span>
  );

  const truncate = (text: string, maxLen: number) =>
    maxLen <= 3
      ? text.slice(0, maxLen)
      : text.length <= maxLen
        ? text
        : text.slice(0, maxLen - 3) + '...';


  const basename = (p: string | undefined) => (typeof p === "string" ? p.split("/").pop() : "");

  const formatProgress = (p: unknown) => {
    if (p === null || p === undefined || p === "") return "—";
    const pct = computePct(p);
    return `${pct}%`;
  };

  const computePct = (p: unknown) => {
    if (p === null || p === undefined || p === "") return 0;
    // Handle "X / Y" format (e.g., "9 / 545") - note spaces around slash from backend
    if (typeof p === 'string' && p.includes('/')) {
      const parts = p.split('/').map(s => s.trim());
      const current = parseFloat(parts[0]);
      const total = parseFloat(parts[1]);
      if (Number.isFinite(current) && Number.isFinite(total) && total > 0) {
        return Math.min(100, Math.max(0, Math.round((current / total) * 100)));
      }
      return 0;
    }
    const num = Number(p);
    if (!Number.isFinite(num)) return 0;
    // Backend sends progress as 0-100 directly, so just clamp it
    return Math.min(100, Math.max(0, Math.round(num)));
  };

  // Visual pct used for display/fill. Prevent briefly showing 100% unless status is Finished
  const displayPct = (p: unknown, status?: string) => {
    const pct = computePct(p);
    const statusNorm = String(status || "").trim();
    // If the backend reports 100% but the job hasn't reached 'Finished', show 99 to avoid flash
    if (pct >= 100 && statusNorm.toLowerCase() !== 'finished') return 99;
    return pct;
  };
  const isFinalizingJob = (job: RequestJob | { progress?: unknown; status?: string }) => {
    const pct = computePct(job.progress);
    const statusNorm = String(job.status || "").trim().toLowerCase();
    // Only treat as finalizing when status is explicitly "Compressing"
    // This is set by the backend only when progress == 100 and tarball isn't ready yet
    return statusNorm === 'compressing';
  };

  const startFinalizingPoll = (jobId: string | number) => {
    if (finalizingPollsRef.current[jobId]) return; // already polling

    let attempts = 0;
    const iv = setInterval(async () => {
      attempts += 1;
      try {
        const updated = await fetchJobDetail(jobId);
        if (updated) {
          // Merge the updated job into requests list so UI refreshes
          setRequests((prev) => prev.map((r) => (r.id === updated.id ? updated : r)));
          // If it's no longer finalizing, stop this poll
          if (!isFinalizingJob(updated)) {
            if (finalizingPollsRef.current[jobId]) {
              clearInterval(finalizingPollsRef.current[jobId] as ReturnType<typeof setInterval>);
              finalizingPollsRef.current[jobId] = null;
            }
          }
        }
      } catch (err) {
        // ignore individual errors; we'll retry a few times
      }
      // safety cap: stop after ~20 attempts (~30s)
      if (attempts >= 20) {
        if (finalizingPollsRef.current[jobId]) {
          clearInterval(finalizingPollsRef.current[jobId] as ReturnType<typeof setInterval>);
          finalizingPollsRef.current[jobId] = null;
        }
      }
    }, 1500);

    finalizingPollsRef.current[jobId] = iv;
  };

  // stop all finalizing polls on unmount
  useEffect(() => {
    return () => {
      Object.values(finalizingPollsRef.current).forEach((iv) => {
        if (iv) clearInterval(iv);
      });
    };
  }, []);

  const progressBarTemplate = (rowData: RequestJob) => {
    const p = rowData.progress;
    if (p === null || p === undefined || p === "") return "—";
    const pctRaw = computePct(p);
    const isFinalizing = isFinalizingJob(rowData);
    const pct = isFinalizing ? 99 : pctRaw;

    const getProgressColor = () => {
      if (rowData.status === "Failed") return "bg-red-500";
      if (rowData.status === "Finished") return "bg-green-500";
      if (isFinalizing) return "bg-yellow-500"; // finalizing indicator
      if (pctRaw < 30) return "bg-blue-400";
      if (pctRaw < 70) return "bg-blue-500";
      return "bg-blue-600";
    };

    // If this job appears to be finalizing, ensure a poll is active to get the real status
    if (isFinalizing) startFinalizingPoll(rowData.id);

    return (
      <div className="rm-progress-container">
        <div className="rm-progress-track" style={{ flex: 1, minWidth: 0 }}>
          <div
            className={`rm-progress-fill ${getProgressColor()} ${isFinalizing ? 'rm-finalizing' : ''}`}
            style={{
              // CSS custom property for progress animation
              ['--rm-progress' as string]: (pct / 100).toString(),
              borderTopRightRadius: pct === 100 ? '999px' : 0,
              borderBottomRightRadius: pct === 100 ? '999px' : 0
            }}
            data-pct={pct}
            aria-valuenow={pctRaw}
            aria-valuemin={0}
            aria-valuemax={100}
          />
        </div>
        <span className="rm-progress-text" style={{ marginLeft: 8, flex: 'none' }}>{pct}%{isFinalizing ? ' (finalizing...)' : ''}</span>
      </div>
    );
  };

  const confirmDelete = (requestId: string | number) => {
    confirmDialog({
      message: "Are you sure you want to delete this request?",
      header: "Confirm Delete",
      icon: "pi pi-exclamation-triangle",
      accept: () => deleteRequest(requestId),
    });
  };

  const deleteRequest = (requestId: string | number) => {
    setRequests((prev) => prev.filter((r) => r.id !== requestId));
    toast.success("Request deleted");
  };

  const actionBodyTemplate = (rowData: RequestJob) => (
    <Button
      color="neutral"
      variant="outlined"
      size="sm"
      sx={{
        color: "#e5e7eb",
        borderColor: "#6b7280",
        '&:hover': { backgroundColor: '#374151', borderColor: '#9ca3af' },
      }}
      onClick={(e) => { e.stopPropagation(); confirmDelete(rowData.id); }}
    >
      Delete
    </Button>
  );

  const handleRowClick = async (e: { data: unknown }) => {
    const rowData = e.data as RequestJob;
    const detail = await fetchJobDetail(rowData.id);
    if (detail) { setSelectedRequest(detail); setIsDialogVisible(true); }
  };

  return (

    <div className="trip-management-container my-10 p-4 sm:p-6 rounded-xl shadow-md
                bg-white dark:bg-neutral-900
                text-neutral-900 dark:text-neutral-100
                border border-neutral-200 dark:border-neutral-700">

      <BreadcrumbNav currentPage="management" />
      <h2 className="text-2xl sm:text-3xl font-bold tracking-tight mb-6">Manage Requests</h2>

      <div className="flex flex-wrap items-center gap-4 mb-6">
        <Dropdown
          value={filterStatus}
          options={[{ label: "All Statuses", value: "all" }, ...STATUS_OPTIONS.map((s) => ({ label: s, value: s }))]}
          onChange={(e) => setFilterStatus(e.value)}
          placeholder="Filter by Status"
          className="min-w-[180px]"
        />
      </div>

      {isLoading ? (
        <div className="table-skeleton">
          {[...Array(5)].map((_, i) => (
            <div key={i} className="skeleton-row">
              <div className="skeleton-cell w-[10%]"><div className="skeleton-bar" /></div>
              <div className="skeleton-cell w-[22%]"><div className="skeleton-bar" /></div>
              <div className="skeleton-cell w-[10%]"><div className="skeleton-bar" /></div>
              <div className="skeleton-cell w-[12%]"><div className="skeleton-bar" /></div>
              <div className="skeleton-cell w-[16%]"><div className="skeleton-bar" /></div>
              <div className="skeleton-cell w-[10%]"><div className="skeleton-bar" /></div>
              <div className="skeleton-cell w-[20%]"><div className="skeleton-bar" /></div>
            </div>
          ))}
        </div>
      ) : (
        <div className="table-wrapper w-full">
          <DataTable
            value={filteredRequests}
            paginator
            rows={10}
            removableSort
            sortMode="multiple"
            emptyMessage={
              <div className="empty-state">
                <i className="pi pi-inbox empty-state-icon" />
                <p className="empty-state-text">No requests found</p>
                <p className="empty-state-subtext">Requests you submit will appear here</p>
              </div>
            }
            onRowClick={handleRowClick}
            resizableColumns={false}
            className="w-full"
            style={{ width: '100%' }}
          >

            <Column
              field="id"
              header="ID"
              body={(row: RequestJob) => (
                <span title={String(row.id)}>
                  {String(row.id).split("-").slice(-1)[0]}
                </span>
              )}
            />
            <Column field="target" header="Target" sortable body={(row: RequestJob) => textWithEllipsis(row.target, "100%")} />
            <Column field="tracks" header="# Tracks" body={(row: RequestJob) => row.tracks} />
            <Column field="status" header="Status" body={statusBodyTemplate} style={{ textAlign: "center" }} sortable />
            <Column field="progress" header="Progress" body={progressBarTemplate} style={{ textAlign: "center" }} sortable />
            <Column
              field="quality"
              header="Quality"
              body={qualityBodyTemplate}
              style={{ textAlign: "center" }}
              sortable />
            <Column
              field="tarball"
              header={
                <span className="flex items-center">
                  <i className="pi pi-download mr-1" />
                  Tarball
                </span>
              }
              body={(row: RequestJob) => {
                const url = tarballUrl(resolveTarballPath(row as RequestJob), row.quality || "FLAC", row.type);
                if (!url) return "—";
                const encodedURL = encodeURI(url);

                const fileName = url.split("/").pop() || "";

                return (
                  <a
                    href={encodedURL}
                    target="_blank"
                    rel="noopener noreferrer"
                    className="truncate text-blue-500 hover:underline"
                    title={fileName}
                  >
                    {truncate(fileName, 28)}
                  </a>
                );
              }}
            />
          </DataTable>
        </div>
      )}

      <ConfirmDialog />

      <Dialog
        header="Request Details"
        visible={isDialogVisible}
        style={{ width: "500px", maxHeight: "90vh" }}
        onHide={() => setIsDialogVisible(false)}
        breakpoints={{ "960px": "95vw" }}
        modal
        dismissableMask
        maximizable
        className="dark:bg-neutral-900 dark:text-neutral-100 request-details-dialog"
      >
        {selectedRequest ? (
          <div className="request-details-content space-y-4 text-sm">

            {/* --- Metadata Card --- */}
            <div className="p-3 bg-gray-100 dark:bg-neutral-800 rounded-md grid grid-cols-1 sm:grid-cols-2 gap-4">
              {selectedRequest.id && <p className="col-span-2 break-all"><strong>ID:</strong> {String(selectedRequest.id)}</p>}
              {selectedRequest.target && <p><strong>Target:</strong> {selectedRequest.target}</p>}
              {selectedRequest.tracks && <p><strong># Tracks:</strong> {selectedRequest.tracks}</p>}
              {selectedRequest.quality && (
                <p>
                  <strong>Quality:</strong>{" "}
                  <span className={`px-2 py-0.5 rounded-full text-xs font-bold ${getQualityColorClass(selectedRequest.quality)}`}>
                    {selectedRequest.quality}
                  </span>
                </p>
              )}
            </div>

            {/* --- Status / Progress Card --- */}
            <div className="p-3 bg-gray-100 dark:bg-neutral-800 rounded-md grid grid-cols-1 sm:grid-cols-2 gap-4">
              {selectedRequest.status && (
                <p>
                  <strong>Status:</strong>{" "}
                  <span className={`px-2 py-0.5 rounded-full text-xs font-bold ${getStatusColorClass(selectedRequest.status)}`}>
                    {selectedRequest.status}
                  </span>
                </p>
              )}
              {selectedRequest.progress !== undefined && selectedRequest.progress !== null && (
                <div className="col-span-2">
                  <strong>Progress:</strong>
                  <div className="rm-progress-container mt-2">
                    <div className="rm-progress-track rm-progress-track-lg">
                      {(() => {
                        const pctRawDialog = computePct(selectedRequest.progress);
                        const isFinalizingDialog = isFinalizingJob(selectedRequest);
                        const pctDialog = isFinalizingDialog ? 99 : pctRawDialog;
                        const status = selectedRequest.status;
                        const fillColor = status === "Failed" ? "bg-red-500" : status === "Finished" ? "bg-green-500" : isFinalizingDialog ? "bg-yellow-500" : "bg-blue-500";

                        // Ensure we poll for finalizing jobs to get the real status update
                        if (isFinalizingDialog) startFinalizingPoll(selectedRequest.id);

                        return (
                          <>
                            <div
                            className={`rm-progress-fill ${fillColor} ${isFinalizingDialog ? 'rm-finalizing' : ''}`}
                              style={{
                                ['--rm-progress' as string]: (pctDialog / 100).toString(),
                                borderTopRightRadius: pctDialog >= 100 ? '999px' : 0,
                                borderBottomRightRadius: pctDialog >= 100 ? '999px' : 0
                              }}
                              data-pct={pctDialog}
                              aria-valuenow={pctRawDialog}
                              aria-valuemin={0}
                              aria-valuemax={100}
                            />
                          </>
                        );
                      })()}
                    </div>
                    <span className="rm-progress-text">{formatProgress(selectedRequest.progress)}{isFinalizingJob(selectedRequest) ? ' — finalizing' : ''}</span>
                  </div>
                </div>
              )}
            </div>

            {/* --- Timestamps Card --- */}
            <div className="p-3 bg-gray-100 dark:bg-neutral-800 rounded-md grid grid-cols-1 gap-2">
              {selectedRequest.created_at && <p><strong>Enqueued:</strong> {new Date(selectedRequest.created_at).toLocaleString()}</p>}
              {(selectedRequest as RequestJob & { started_at?: string }).started_at && <p><strong>Started:</strong> {new Date((selectedRequest as RequestJob & { started_at: string }).started_at).toLocaleString()}</p>}
              {(selectedRequest as RequestJob & { ended_at?: string }).ended_at && <p><strong>Ended:</strong> {new Date((selectedRequest as RequestJob & { ended_at: string }).ended_at).toLocaleString()}</p>}
            </div>

            {/* --- Tarball Card --- */}
            {
              selectedRequest.tarball && (
                <div className="p-3 bg-gray-100 dark:bg-neutral-800 rounded-md">
                  <p>
                    <strong>Tarball:</strong>{" "}
                    <a
                      href={encodeURI(tarballUrl(resolveTarballPath(selectedRequest), selectedRequest.quality, selectedRequest.type) || "")}
                      target="_blank"
                      rel="noopener noreferrer"
                      className="text-blue-500 hover:underline"
                    >
                      {tarballUrl(resolveTarballPath(selectedRequest), selectedRequest.quality, selectedRequest.type)?.split("/").pop()}
                    </a>
                  </p>
                </div>
              )
            }

            {/* --- Track List Card --- */}
            {selectedRequest.track_list && selectedRequest.track_list.length > 0 && (
              <div className="p-3 bg-gray-100 dark:bg-neutral-800 rounded-md track-list-card">
                <p className="mb-2 flex-shrink-0"><strong>Tracks ({selectedRequest.track_list.length}):</strong></p>
                <div className="track-list-scrollable space-y-2" data-lenis-prevent>
                  {selectedRequest.track_list.map((track, idx) => {
                    const rawStatus = String(track.status || "pending");
                    const statusNorm = rawStatus.trim().toLowerCase();

                    const isError = statusNorm === "failed" || statusNorm === "error" || !!track.error;
                    const isSuccess = ["done", "success", "completed", "finished"].includes(statusNorm);
                    const isPending = ["pending", "queued"].includes(statusNorm);
                    const isDownloading = ["downloading", "in_progress", "started"].includes(statusNorm);

                    const statusBadgeClass = isError
                      ? "bg-red-600 text-white"
                      : isSuccess
                        ? "bg-green-600 text-white"
                        : isDownloading
                          ? "bg-blue-600 text-white"
                          : isPending
                            ? "bg-yellow-600 text-white"
                            : "bg-gray-500 text-white";

                    const trackTitle = track.title || track.filename || `Track ${idx + 1}`;
                    const trackArtist = track.artist;

                    return (
                      <div
                        key={idx}
                        className={`p-2 rounded border ${isError ? "border-red-500/50 bg-red-500/10" : "border-neutral-300 dark:border-neutral-600"}`}
                      >
                        <div className="flex items-start justify-between gap-2">
                          <div className="flex-1 min-w-0">
                            <p className="text-sm font-medium truncate" title={trackTitle}>
                              {trackTitle}
                            </p>
                            {trackArtist && (
                              <p className="text-xs text-neutral-500 dark:text-neutral-400 truncate" title={trackArtist}>
                                {trackArtist}
                              </p>
                            )}
                          </div>
                          <span className={`px-2 py-0.5 pr-3 mr-2 rounded text-xs font-semibold whitespace-nowrap ${statusBadgeClass} rm-track-status`} title={rawStatus}>
                            {rawStatus}
                          </span>
                        </div>
                        {track.error && (
                          <p className="mt-1 text-xs text-red-400 break-words">
                            <i className="pi pi-exclamation-triangle mr-1" />
                            {track.error}
                          </p>
                        )}
                      </div>
                    );
                  })}
                </div>
              </div>
            )}

          </div >
        ) : (
          <p>Loading...</p>
        )
        }
      </Dialog >


    </div >
  );
}
