import { useCallback, useEffect, useRef, useState } from "react";
import { Info, Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Progress } from "@/components/ui/progress";
import { cn } from "@/lib/utils";
import { useContentJob } from "@/hooks/useContentJob";
import { getContentJobAsset } from "@/api/contentJobs";
import {
  submitConvertJob,
  getConverterCapabilities,
  type ConverterCapabilities,
  type ConvertMimeError,
} from "@/api/convert";

// ---------------------------------------------------------------------------
// Format groups
// ---------------------------------------------------------------------------

export const FORMAT_GROUPS: Record<string, string[]> = {
  Images: ["png", "jpg", "svg", "webp", "gif"],
  "Audio/Video": ["mp3", "mp4", "wav", "ogg", "webm"],
  Documents: ["md", "html", "pdf", "docx"],
  Data: ["csv", "json", "xlsx"],
};

// All formats as a flat list for validation
const ALL_FORMATS = Object.values(FORMAT_GROUPS).flat();

interface ConvertBundle {
  type: "convert-bundle";
  outputFilename: string;
  outputMime: string;
  outputBase64: string;
  method: "direct" | "ai-bridge";
}

function formatBytes(bytes: number): string {
  if (bytes < 1024) return `${bytes} B`;
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}

function isConvertMimeError(result: object): result is ConvertMimeError {
  return "error" in result && "actualMime" in result;
}

// ---------------------------------------------------------------------------
// Determine whether a format group requires a direct converter
// ---------------------------------------------------------------------------

function groupNeedsDirectConverter(group: string, capabilities: ConverterCapabilities): boolean {
  switch (group) {
    case "Images":
      return !capabilities.imageConverter;
    case "Audio/Video":
      return !capabilities.audioVideoConverter;
    case "Documents":
      return !capabilities.docConverter;
    case "Data":
      return !capabilities.dataConverter;
    default:
      return false;
  }
}

function targetGroupForFormat(format: string): string | null {
  for (const [group, formats] of Object.entries(FORMAT_GROUPS)) {
    if (formats.includes(format.toLowerCase())) return group;
  }
  return null;
}

// ---------------------------------------------------------------------------
// Props
// ---------------------------------------------------------------------------

interface ConvertPanelProps {
  initialSourceFormat?: string;
  initialTargetFormat?: string;
  companyId: string;
}

// ---------------------------------------------------------------------------
// Component
// ---------------------------------------------------------------------------

export function ConvertPanel({ initialSourceFormat, initialTargetFormat, companyId }: ConvertPanelProps) {
  const [file, setFile] = useState<File | null>(null);
  const [targetFormat, setTargetFormat] = useState<string | null>(
    initialTargetFormat ? initialTargetFormat.toLowerCase() : null,
  );
  const [mimeError, setMimeError] = useState<ConvertMimeError | null>(null);
  const [dragOver, setDragOver] = useState(false);
  const [capabilities, setCapabilities] = useState<ConverterCapabilities | null>(null);
  const [convertBundle, setConvertBundle] = useState<ConvertBundle | null>(null);
  const [isSubmitting, setIsSubmitting] = useState(false);

  const fileInputRef = useRef<HTMLInputElement>(null);
  const job = useContentJob(companyId);

  // Normalize initialSourceFormat
  const normalizedSourceFormat = initialSourceFormat
    ? ALL_FORMATS.includes(initialSourceFormat.toLowerCase())
      ? initialSourceFormat.toLowerCase()
      : null
    : null;

  // Fetch converter capabilities on mount
  useEffect(() => {
    getConverterCapabilities()
      .then(setCapabilities)
      .catch(() => {
        // Silently fall back — all formats will show AI bridge notice
        setCapabilities({
          imageConverter: false,
          audioVideoConverter: false,
          docConverter: false,
          dataConverter: false,
        });
      });
  }, []);

  // Fetch asset bundle when job completes
  useEffect(() => {
    if (job.status === "done" && job.resultAssetId && !convertBundle) {
      void getContentJobAsset(companyId, job.resultAssetId).then(async (assetUrl) => {
        const res = await fetch(assetUrl);
        const text = await res.text();
        try {
          const parsed = JSON.parse(text) as ConvertBundle;
          setConvertBundle(parsed);
        } catch {
          // ignore parse error — error state will show
        }
      });
    }
  }, [job.status, job.resultAssetId, convertBundle, companyId]);

  // ---------------------------------------------------------------------------
  // File selection helpers
  // ---------------------------------------------------------------------------

  function handleFileSelect(selected: File) {
    setFile(selected);
    setMimeError(null);
    setConvertBundle(null);
  }

  function handleDragOver(e: React.DragEvent) {
    e.preventDefault();
    e.stopPropagation();
    setDragOver(true);
  }

  function handleDragLeave(e: React.DragEvent) {
    e.preventDefault();
    e.stopPropagation();
    if (e.currentTarget === e.target || !e.currentTarget.contains(e.relatedTarget as Node)) {
      setDragOver(false);
    }
  }

  function handleDrop(e: React.DragEvent) {
    e.preventDefault();
    e.stopPropagation();
    setDragOver(false);
    const dropped = e.dataTransfer.files[0];
    if (dropped) handleFileSelect(dropped);
  }

  function handleKeyDown(e: React.KeyboardEvent) {
    if (e.key === "Enter" || e.key === " ") {
      e.preventDefault();
      fileInputRef.current?.click();
    }
  }

  function handleInputChange(e: React.ChangeEvent<HTMLInputElement>) {
    const selected = e.target.files?.[0];
    if (selected) handleFileSelect(selected);
    // Reset input so same file can be reselected
    e.target.value = "";
  }

  // ---------------------------------------------------------------------------
  // Convert action
  // ---------------------------------------------------------------------------

  const handleConvert = useCallback(async () => {
    if (!file || !targetFormat) return;
    setMimeError(null);
    setConvertBundle(null);
    setIsSubmitting(true);

    try {
      const result = await submitConvertJob(companyId, file, targetFormat);
      if (isConvertMimeError(result)) {
        setMimeError(result);
        setIsSubmitting(false);
        return;
      }
      // Track job via SSE — the hook handles polling
      // We need to reuse the SSE pattern, but submitConvertJob already submitted.
      // Re-submit via the job hook SSE tracking by connecting the existing jobId.
      // However, useContentJob.submit() calls submitContentJob internally.
      // For convert, we already have the jobId — connect SSE directly.
      void connectJobSse(result.jobId);
    } catch (err) {
      setIsSubmitting(false);
      job.reset();
    }
  }, [file, targetFormat, companyId]);

  // Connect SSE for an already-submitted job
  const connectJobSse = useCallback(
    async (jobId: string) => {
      // Directly connect SSE without re-submitting
      // We'll track progress state manually via EventSource
      setIsSubmitting(false);
      // Leverage the hook's internal submit by injecting: since useContentJob doesn't
      // expose a "track existing job" method, we call submit with a dummy type that
      // matches — but the convert route already dispatched the job.
      // Instead, manage SSE here directly (same pattern as useContentJob internals).
      const url = `/api/companies/${companyId}/content-jobs/${jobId}/events`;
      const es = new EventSource(url, { withCredentials: true });

      es.addEventListener("status", (e: MessageEvent) => {
        const data = JSON.parse(e.data as string) as {
          status?: string;
          resultAssetId?: string | null;
          errorMessage?: string | null;
        };

        if (data.status === "done" || data.status === "failed") {
          es.close();
          if (data.status === "done" && data.resultAssetId) {
            void getContentJobAsset(companyId, data.resultAssetId).then(async (assetUrl) => {
              const res = await fetch(assetUrl);
              const text = await res.text();
              try {
                const parsed = JSON.parse(text) as ConvertBundle;
                setConvertBundle(parsed);
              } catch {
                // ignore
              }
            });
          }
        }
        // Sync progress into job state via the hook's submit? Not possible.
        // Track progress via local state here.
        setJobProgress(data.status ?? "queued");
      });

      es.addEventListener("error", () => {
        es.close();
        setJobProgressState("failed");
      });
    },
    [companyId],
  );

  // Local SSE state (since we bypass useContentJob for convert submissions)
  type LocalJobStatus = "idle" | "queued" | "running" | "done" | "failed";
  const [jobProgressState, setJobProgressState] = useState<LocalJobStatus>("idle");

  function setJobProgress(status: string) {
    setJobProgressState(status as LocalJobStatus);
  }

  function progressValue(): number {
    switch (jobProgressState) {
      case "queued":
        return 5;
      case "running":
        return 50;
      case "done":
        return 100;
      default:
        return 0;
    }
  }

  const isConverting = isSubmitting || jobProgressState === "queued" || jobProgressState === "running";
  const canConvert = file !== null && targetFormat !== null && !isConverting;

  // ---------------------------------------------------------------------------
  // AI fallback notice
  // ---------------------------------------------------------------------------

  function showAiFallbackNotice(): boolean {
    if (!capabilities || !targetFormat) return false;
    const group = targetGroupForFormat(targetFormat);
    if (!group) return false;
    return groupNeedsDirectConverter(group, capabilities);
  }

  // ---------------------------------------------------------------------------
  // Download handler
  // ---------------------------------------------------------------------------

  function handleDownload() {
    if (!convertBundle) return;
    const binary = atob(convertBundle.outputBase64);
    const bytes = new Uint8Array(binary.length);
    for (let i = 0; i < binary.length; i++) {
      bytes[i] = binary.charCodeAt(i);
    }
    const blob = new Blob([bytes], { type: convertBundle.outputMime });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = convertBundle.outputFilename;
    a.click();
    URL.revokeObjectURL(url);
  }

  // ---------------------------------------------------------------------------
  // Drag-drop zone copy
  // ---------------------------------------------------------------------------

  function dropZoneIdleCopy(): string {
    if (normalizedSourceFormat) {
      return `Drop a ${normalizedSourceFormat.toUpperCase()} file here or click to browse`;
    }
    return "Drop a file here or click to browse";
  }

  // ---------------------------------------------------------------------------
  // Drag-drop zone classes
  // ---------------------------------------------------------------------------

  function dropZoneClasses(): string {
    const base =
      "relative flex min-h-[120px] cursor-pointer flex-col items-center justify-center gap-2 rounded-md border-2 border-dashed p-6 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring";
    const reducedMotionTransition = "motion-safe:transition-colors motion-safe:duration-150";

    if (mimeError) {
      return cn(base, reducedMotionTransition, "border-destructive bg-destructive/10");
    }
    if (dragOver) {
      return cn(base, "border-primary bg-accent/30");
    }
    return cn(base, reducedMotionTransition, "border-border bg-secondary");
  }

  // ---------------------------------------------------------------------------
  // Render
  // ---------------------------------------------------------------------------

  return (
    <div className="flex flex-col gap-6">
      {/* Two-column layout: source left, target right */}
      <div className="grid grid-cols-1 gap-6 md:grid-cols-2">
        {/* ConvertSourceZone */}
        <div>
          <p className="mb-2 text-sm font-medium">Source File</p>
          <div
            role="button"
            tabIndex={0}
            aria-label="Upload file — drop here or press Enter to browse"
            className={dropZoneClasses()}
            onDragOver={handleDragOver}
            onDragLeave={handleDragLeave}
            onDrop={handleDrop}
            onKeyDown={handleKeyDown}
            onClick={() => fileInputRef.current?.click()}
          >
            <input
              ref={fileInputRef}
              type="file"
              className="hidden"
              onChange={handleInputChange}
              tabIndex={-1}
            />

            {dragOver ? (
              <p className="text-sm text-primary font-medium">Release to select</p>
            ) : mimeError ? (
              <p className="text-sm text-destructive text-center">
                File extension does not match content. Got {mimeError.actualMime}, expected{" "}
                {mimeError.claimedMime}.
              </p>
            ) : file ? (
              <div className="flex flex-col items-center gap-1 text-center">
                <p className="text-sm font-medium">{file.name}</p>
                <p className="text-xs text-muted-foreground">{formatBytes(file.size)}</p>
                <p className="font-mono text-xs text-muted-foreground">{file.type || "unknown"}</p>
              </div>
            ) : (
              <p className="text-sm text-muted-foreground text-center">{dropZoneIdleCopy()}</p>
            )}
          </div>
        </div>

        {/* ConvertTargetSelector */}
        <div>
          <p className="mb-2 text-sm font-medium">Target Format</p>
          <div
            role="radiogroup"
            aria-label="Target format"
            className="flex flex-col gap-4"
          >
            {Object.entries(FORMAT_GROUPS).map(([group, formats]) => (
              <div key={group}>
                <p className="mb-1.5 text-sm font-medium text-muted-foreground">{group}</p>
                <div className="flex flex-wrap gap-2">
                  {formats.map((fmt) => (
                    <button
                      key={fmt}
                      type="button"
                      role="radio"
                      aria-checked={targetFormat === fmt}
                      aria-label={fmt.toUpperCase()}
                      className={cn(
                        "rounded-full px-3 py-1 text-xs font-medium transition-colors",
                        targetFormat === fmt
                          ? "bg-primary text-primary-foreground"
                          : "bg-muted text-muted-foreground hover:bg-muted/80",
                      )}
                      onClick={() => setTargetFormat(fmt)}
                    >
                      {fmt.toUpperCase()}
                    </button>
                  ))}
                </div>
              </div>
            ))}
          </div>

          {/* AI fallback notice */}
          {showAiFallbackNotice() && (
            <div className="mt-3 flex items-center gap-2 rounded-md bg-secondary p-3">
              <Info className="h-[14px] w-[14px] shrink-0 text-muted-foreground" />
              <p className="text-xs text-muted-foreground">
                No direct converter for this pair — AI bridge will be used.
              </p>
            </div>
          )}
        </div>
      </div>

      {/* ConvertActionBar */}
      <div className="flex flex-col gap-3">
        {convertBundle ? (
          <Button onClick={handleDownload}>
            Download {convertBundle.outputFilename}
          </Button>
        ) : jobProgressState === "failed" ? (
          <>
            <p className="text-sm text-destructive">
              Conversion failed — {job.errorMessage ?? "Unknown error"}. Try again.
            </p>
            <Button
              disabled={!canConvert}
              onClick={() => {
                setJobProgressState("idle");
                void handleConvert();
              }}
            >
              Convert File
            </Button>
          </>
        ) : isConverting ? (
          <>
            <Button disabled>
              <Loader2 className="mr-2 h-4 w-4 animate-spin" />
              Converting...
            </Button>
            <Progress value={progressValue()} className="h-2" />
          </>
        ) : jobProgressState === "idle" && !file && !targetFormat ? (
          <div className="flex flex-col gap-1">
            <p className="text-base font-semibold text-muted-foreground">No conversion yet</p>
            <p className="text-sm text-muted-foreground">
              Upload a file and choose a target format to convert.
            </p>
            <Button disabled className="mt-2 self-start">
              Convert File
            </Button>
          </div>
        ) : (
          <Button disabled={!canConvert} onClick={() => void handleConvert()}>
            Convert File
          </Button>
        )}
      </div>
    </div>
  );
}
