"use client";

import { useCallback, useEffect, useRef, useState } from "react";
import type { Artifact, Skill } from "@/lib/types";
import {
  estimateToolCostAPI,
  submitJobAPI,
  getJobStatusAPI,
  type CostEstimate,
} from "@/lib/api";

function detectMime(key: string): string {
  if (key.endsWith('.pdb') || key.endsWith('.cif')) return 'chemical/x-pdb';
  if (key.endsWith('.fasta') || key.endsWith('.fa')) return 'application/fasta';
  if (key.endsWith('.csv')) return 'text/csv';
  if (key.endsWith('.json')) return 'application/json';
  return 'application/octet-stream';
}

type RunState = "configure" | "submitting" | "polling" | "completed" | "failed";

interface Props {
  skill: Skill;
  campaignId: string;
  onComplete: (artifacts: Artifact[]) => void;
  onClose: () => void;
}

export default function ToolRunPanel({
  skill,
  campaignId,
  onComplete,
  onClose,
}: Props) {
  const [runState, setRunState] = useState<RunState>("configure");
  const [estimate, setEstimate] = useState<CostEstimate | null>(null);
  const [estimateError, setEstimateError] = useState<string | null>(null);
  const [callParams, setCallParams] = useState("{}");
  const [file, setFile] = useState<File | null>(null);
  const [provider, setProvider] = useState<"runpod" | "slurm">("runpod");
  const [error, setError] = useState<string | null>(null);
  const [artifacts, setArtifacts] = useState<Artifact[]>([]);
  const [elapsed, setElapsed] = useState(0);
  const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
  const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
  const jobIdRef = useRef<string | null>(null);

  // Fetch cost estimate on mount
  useEffect(() => {
    let cancelled = false;
    estimateToolCostAPI(skill.id, skill.gpuType)
      .then((est) => {
        if (!cancelled) setEstimate(est);
      })
      .catch((e) => {
        if (!cancelled)
          setEstimateError(e instanceof Error ? e.message : "estimate_failed");
      });
    return () => {
      cancelled = true;
    };
  }, [skill.id, skill.gpuType]);

  // Clean up intervals on unmount
  useEffect(() => {
    return () => {
      if (pollRef.current) clearInterval(pollRef.current);
      if (timerRef.current) clearInterval(timerRef.current);
    };
  }, []);

  const startPolling = useCallback(
    (jobId: string) => {
      setRunState("polling");
      setElapsed(0);
      const start = Date.now();

      timerRef.current = setInterval(() => {
        setElapsed(Math.floor((Date.now() - start) / 1000));
      }, 1000);

      pollRef.current = setInterval(async () => {
        try {
          const status = await getJobStatusAPI(jobId);
          const hubStatus = String(
            status.hub?.status ?? "",
          ).toLowerCase();

          if (hubStatus === "completed") {
            if (pollRef.current) clearInterval(pollRef.current);
            if (timerRef.current) clearInterval(timerRef.current);

            // Map backend artifact shape to frontend Artifact type.
            // Artifacts from Hub jobs are served by the backend, not the
            // Next.js local artifact store. Proxy via the backend URL.
            const hubOutputData =
              status.hub?.output_data && typeof status.hub.output_data === "object"
                ? (status.hub.output_data as Record<string, unknown>)
                : undefined;
            const mapped: Artifact[] = (status.artifacts ?? []).map(
              (a, i) => ({
                id: a.id ?? `artifact-${i}`,
                name: a.hub_output_key ?? `output-${i}`,
                bytes: 0,
                mime: detectMime(a.hub_output_key ?? ''),
                url: `/api/jobs/${encodeURIComponent(jobId!)}/artifacts/${encodeURIComponent(a.id ?? "")}`,
                createdAt: Date.now(),
                hubOutputData,
              }),
            );
            setArtifacts(mapped);
            setRunState("completed");
            onComplete(mapped);
          } else if (
            hubStatus === "failed" ||
            hubStatus === "error" ||
            hubStatus === "cancelled"
          ) {
            if (pollRef.current) clearInterval(pollRef.current);
            if (timerRef.current) clearInterval(timerRef.current);
            setError(
              String(
                status.hub?.error ??
                  status.hub?.message ??
                  `Job ${hubStatus}`,
              ),
            );
            setRunState("failed");
          }
        } catch (e) {
          // Don't stop polling on transient network errors
          console.warn("Poll error:", e);
        }
      }, 3000);
    },
    [onComplete],
  );

  const handleRun = useCallback(async () => {
    if (!file) {
      setError("Please select an input file.");
      return;
    }

    // Validate JSON
    try {
      JSON.parse(callParams);
    } catch {
      setError("call_params is not valid JSON.");
      return;
    }

    setError(null);
    setRunState("submitting");

    try {
      const formData = new FormData();
      formData.set("endpoint_name", skill.id);
      formData.set("provider", provider);
      formData.set("call_params", callParams);
      formData.set("input_file", file);

      const result = await submitJobAPI(campaignId, formData);
      jobIdRef.current = result.campaign_job_id;
      startPolling(result.campaign_job_id);
    } catch (e) {
      setError(e instanceof Error ? e.message : "Submission failed");
      setRunState("failed");
    }
  }, [file, callParams, skill.id, provider, campaignId, startPolling]);

  const isRunning = runState === "submitting" || runState === "polling";

  return (
    <div
      data-testid="tool-run-panel"
      className="fixed inset-0 z-[70] flex justify-end bg-black/30 backdrop-blur-sm"
      onClick={onClose}
    >
      <div
        className="w-[560px] max-w-[96vw] h-full bg-white border-l border-border shadow-xl flex flex-col"
        onClick={(e) => e.stopPropagation()}
      >
        {/* Header */}
        <div className="px-4 py-3 border-b border-border flex items-start gap-3">
          <div className="min-w-0 flex-1">
            <p className="text-sm font-semibold text-foreground truncate">
              Run {skill.name}
            </p>
            <p className="text-[10px] text-muted-fg font-mono truncate">
              {skill.id}
            </p>
          </div>
          <button
            onClick={onClose}
            className="text-[11px] text-muted-fg hover:text-foreground"
            aria-label="Close"
          >
            ✕
          </button>
        </div>

        {/* Body */}
        <div className="flex-1 overflow-y-auto px-4 py-3 space-y-4">
          {/* Description */}
          {skill.description && (
            <p className="text-[11px] text-foreground leading-relaxed">
              {skill.description}
            </p>
          )}

          {/* Cost estimate */}
          <section>
            <h3 className="text-[10px] uppercase tracking-wider text-muted-fg font-semibold mb-1">
              Cost Estimate
            </h3>
            {estimate ? (
              <div className="grid grid-cols-3 gap-2 text-[11px]">
                <div>
                  <p className="text-[10px] text-muted-fg">Cost</p>
                  <p
                    data-testid="estimate-cost"
                    className={`font-medium ${estimate.cost_alert ? "text-red-600" : "text-foreground"}`}
                  >
                    ${estimate.estimated_usd.toFixed(2)}
                  </p>
                </div>
                <div>
                  <p className="text-[10px] text-muted-fg">GPU</p>
                  <p className="text-foreground">{estimate.gpu_type || "CPU"}</p>
                </div>
                <div>
                  <p className="text-[10px] text-muted-fg">Est. Time</p>
                  <p className="text-foreground">
                    {estimate.minutes_used.toFixed(1)} min
                  </p>
                </div>
              </div>
            ) : estimateError ? (
              <p className="text-[11px] text-muted-fg">
                Estimate unavailable
              </p>
            ) : (
              <p className="text-[11px] text-muted-fg">Loading estimate...</p>
            )}
          </section>

          {/* Configuration (only visible before run) */}
          {runState === "configure" && (
            <>
              {/* Provider */}
              <section>
                <h3 className="text-[10px] uppercase tracking-wider text-muted-fg font-semibold mb-1">
                  Provider
                </h3>
                <div className="flex gap-2">
                  {(["runpod", "slurm"] as const).map((p) => (
                    <button
                      key={p}
                      onClick={() => setProvider(p)}
                      className={`text-[11px] px-3 py-1.5 rounded-md border transition-colors ${
                        provider === p
                          ? "border-accent bg-accent/10 text-accent font-medium"
                          : "border-border text-muted-fg hover:border-accent/30"
                      }`}
                    >
                      {p}
                    </button>
                  ))}
                </div>
              </section>

              {/* Input file */}
              <section>
                <h3 className="text-[10px] uppercase tracking-wider text-muted-fg font-semibold mb-1">
                  Input File
                </h3>
                <input
                  type="file"
                  data-testid="tool-run-file-input"
                  onChange={(e) => setFile(e.target.files?.[0] ?? null)}
                  className="text-[11px] text-foreground file:mr-3 file:px-3 file:py-1.5 file:rounded-md file:border file:border-border file:bg-muted file:text-[11px] file:text-foreground file:cursor-pointer"
                />
              </section>

              {/* Call params */}
              <section>
                <h3 className="text-[10px] uppercase tracking-wider text-muted-fg font-semibold mb-1">
                  Parameters (JSON)
                </h3>
                <textarea
                  data-testid="tool-run-params"
                  value={callParams}
                  onChange={(e) => setCallParams(e.target.value)}
                  rows={4}
                  className="w-full rounded-md border border-border bg-white px-3 py-2 text-xs font-mono focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent/20 resize-y"
                  placeholder='{"key": "value"}'
                />
              </section>
            </>
          )}

          {/* Running state */}
          {isRunning && (
            <section data-testid="tool-run-polling" className="py-6 text-center space-y-3">
              <div className="w-8 h-8 mx-auto rounded-full border-2 border-accent border-t-transparent animate-spin" />
              <p className="text-xs text-foreground font-medium">
                {runState === "submitting"
                  ? "Submitting..."
                  : `Running on ${provider}...`}
              </p>
              {runState === "polling" && (
                <p className="text-[11px] text-muted-fg">
                  Elapsed: {elapsed}s
                </p>
              )}
            </section>
          )}

          {/* Completed */}
          {runState === "completed" && (
            <section data-testid="tool-run-completed" className="space-y-2">
              <div className="flex items-center gap-2">
                <div className="w-5 h-5 rounded-full bg-success/10 flex items-center justify-center">
                  <svg
                    className="w-3 h-3 text-success"
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    strokeWidth={3}
                  >
                    <polyline points="20 6 9 17 4 12" />
                  </svg>
                </div>
                <p className="text-xs font-medium text-foreground">
                  Completed
                </p>
              </div>
              {artifacts.length > 0 && (
                <ul className="space-y-1 pl-7">
                  {artifacts.map((a) => (
                    <li
                      key={a.id}
                      className="text-[11px] text-foreground font-mono"
                    >
                      {a.name}
                    </li>
                  ))}
                </ul>
              )}
            </section>
          )}

          {/* Error */}
          {error && (
            <div
              data-testid="tool-run-error"
              className="rounded-md border border-red-200 bg-red-50 px-3 py-2"
            >
              <p className="text-[11px] text-red-700">{error}</p>
            </div>
          )}
        </div>

        {/* Footer */}
        <div className="px-4 py-3 border-t border-border flex justify-end gap-2">
          {runState === "configure" && (
            <button
              data-testid="tool-run-button"
              onClick={handleRun}
              disabled={!file}
              className="px-4 py-2 rounded-lg bg-accent text-white text-xs font-medium hover:bg-accent-hover disabled:opacity-40 transition-colors"
            >
              Run
            </button>
          )}
          {(runState === "completed" || runState === "failed") && (
            <button
              onClick={onClose}
              className="px-4 py-2 rounded-lg bg-accent text-white text-xs font-medium hover:bg-accent-hover transition-colors"
            >
              Done
            </button>
          )}
        </div>
      </div>
    </div>
  );
}
