"use client";

import { useCallback, useEffect, useRef, useState } from "react";
import { useRouter } from "next/navigation";
import { Privacy } from "@prisma/client";
import { filenameWithoutExtension } from "@/lib/constants";
import { audioTagsToMetadata } from "@/lib/audio-tags";
import { resolveYouTubeTitle } from "@/lib/titles";
import { SONG_TITLE_MAX } from "@/lib/layout";
import type { ItemMetadata } from "@/lib/types";
import { DEFAULT_LAYOUT, isLowerCornerTemplate, type LayoutSettings } from "@/lib/layout";
import { DEFAULT_WATERMARK, type WatermarkSettings } from "@/lib/watermark";
import { CategorySelect } from "./CategorySelect";
import { LayoutStudio } from "./LayoutStudio";
import { PlaylistSelect } from "./PlaylistSelect";
import { PrivacyToggle } from "./PrivacyToggle";
import { ResolutionSelect } from "./ResolutionSelect";

const UPLOAD_CONCURRENCY = 4;

type AudioItem = {
  id: string;
  file: File;
  path: string | null;
  uploading: boolean;
  itemImagePath: string | null;
  itemImageName: string | null;
  itemImagePreview: string | null;
  metadata: ItemMetadata;
};

function defaultMetadata(title = ""): ItemMetadata {
  return {
    title,
    description: "",
    tags: "",
    privacy: "PUBLIC" as Privacy,
    categoryId: "10",
    resolution: "1280x720",
    notifySubscribers: true,
    madeForKids: false,
    embeddable: true,
    creativeCommons: false,
    includeWatermark: true,
    playlistId: null,
    imagePath: null,
    artist: null,
    songTitle: null,
    watermark: { ...DEFAULT_WATERMARK },
    layout: { ...DEFAULT_LAYOUT },
  };
}

export function UploadForm() {
  const router = useRouter();
  const uploadSessionRef = useRef<string>(crypto.randomUUID());
  const [imageFile, setImageFile] = useState<File | null>(null);
  const [imagePath, setImagePath] = useState<string | null>(null);
  const [imagePreviewUrl, setImagePreviewUrl] = useState<string | null>(null);
  const [imageUploading, setImageUploading] = useState(false);
  const [audioItems, setAudioItems] = useState<AudioItem[]>([]);
  const [submitting, setSubmitting] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [playlistId, setPlaylistId] = useState("");
  const [jobWatermark, setJobWatermark] = useState<WatermarkSettings>({ ...DEFAULT_WATERMARK });
  const [jobLayout, setJobLayout] = useState<LayoutSettings>({ ...DEFAULT_LAYOUT });
  const [logoPreviewUrl, setLogoPreviewUrl] = useState<string | null>(null);
  const [backgroundImagePath, setBackgroundImagePath] = useState<string | null>(null);
  const [backgroundPreviewUrl, setBackgroundPreviewUrl] = useState<string | null>(null);
  const [quota, setQuota] = useState<{
    maxBatchSize: number;
    used: number;
  } | null>(null);

  const loadQuota = useCallback(async () => {
    const res = await fetch("/api/quota");
    if (res.ok) {
      const data = await res.json();
      setQuota({
        maxBatchSize: data.maxBatchSize,
        used: data.used ?? 0,
      });
    }
  }, []);

  useEffect(() => {
    loadQuota();
  }, [loadQuota]);

  useEffect(() => {
    return () => {
      if (imagePreviewUrl) URL.revokeObjectURL(imagePreviewUrl);
      if (logoPreviewUrl) URL.revokeObjectURL(logoPreviewUrl);
      if (backgroundPreviewUrl) URL.revokeObjectURL(backgroundPreviewUrl);
      audioItems.forEach((a) => {
        if (a.itemImagePreview) URL.revokeObjectURL(a.itemImagePreview);
      });
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps -- cleanup on unmount only
  }, []);

  async function uploadFile(
    file: File,
    type: "image" | "audio" | "logo" | "font",
  ): Promise<{ path: string; audioTags?: Parameters<typeof audioTagsToMetadata>[0] | null }> {
    const formData = new FormData();
    formData.append("file", file);
    formData.append("type", type);
    formData.append("session", uploadSessionRef.current);
    const res = await fetch("/api/upload", { method: "POST", body: formData });
    const data = await res.json();
    if (!res.ok) {
      throw new Error(data.error || "Upload failed");
    }
    return { path: data.path, audioTags: data.audioTags };
  }

  async function uploadFilesWithConcurrency(files: File[]) {
    const pending = files.map((file) => {
      const id = crypto.randomUUID();
      const autoTitle = filenameWithoutExtension(file.name);

      setAudioItems((prev) => [
        ...prev,
        {
          id,
          file,
          path: null,
          uploading: true,
          itemImagePath: null,
          itemImageName: null,
          itemImagePreview: null,
          metadata: {
            ...defaultMetadata(autoTitle),
            playlistId: playlistId || null,
            watermark: { ...jobWatermark },
            layout: { ...jobLayout },
          },
        },
      ]);

      return { id, file, autoTitle };
    });

    for (let i = 0; i < pending.length; i += UPLOAD_CONCURRENCY) {
      const chunk = pending.slice(i, i + UPLOAD_CONCURRENCY);
      const results = await Promise.allSettled(
        chunk.map(async ({ id, file, autoTitle }) => {
          const upload = await uploadFile(file, "audio");
          const tagMetadata = upload.audioTags
            ? audioTagsToMetadata(upload.audioTags, autoTitle)
            : null;

          setAudioItems((prev) =>
            prev.map((item) =>
              item.id === id
                ? {
                    ...item,
                    path: upload.path,
                    uploading: false,
                    metadata: tagMetadata
                      ? { ...item.metadata, ...tagMetadata }
                      : item.metadata,
                  }
                : item,
            ),
          );
        }),
      );

      for (let j = 0; j < results.length; j++) {
        if (results[j].status === "rejected") {
          const { id } = chunk[j];
          const reason = results[j] as PromiseRejectedResult;
          setError(reason.reason instanceof Error ? reason.reason.message : "Audio upload failed");
          setAudioItems((prev) => prev.filter((item) => item.id !== id));
        }
      }
    }
  }

  async function handleImageChange(e: React.ChangeEvent<HTMLInputElement>) {
    const file = e.target.files?.[0];
    if (!file) return;
    setError(null);
    setImageFile(file);
    setImageUploading(true);
    if (imagePreviewUrl) URL.revokeObjectURL(imagePreviewUrl);
    setImagePreviewUrl(URL.createObjectURL(file));
    try {
      const path = await uploadFile(file, "image");
      setImagePath(path.path);
    } catch (err) {
      setError(err instanceof Error ? err.message : "Image upload failed");
      setImageFile(null);
      setImagePath(null);
      if (imagePreviewUrl) URL.revokeObjectURL(imagePreviewUrl);
      setImagePreviewUrl(null);
    } finally {
      setImageUploading(false);
    }
  }

  async function handleItemImageChange(itemId: string, file: File | null) {
    if (!file) return;
    setError(null);
    const preview = URL.createObjectURL(file);
    setAudioItems((prev) =>
      prev.map((item) => {
        if (item.id !== itemId) return item;
        if (item.itemImagePreview) URL.revokeObjectURL(item.itemImagePreview);
        return {
          ...item,
          itemImageName: file.name,
          itemImagePreview: preview,
          itemImagePath: null,
        };
      }),
    );
    try {
      const uploaded = await uploadFile(file, "image");
      setAudioItems((prev) =>
        prev.map((item) =>
          item.id === itemId
            ? {
                ...item,
                itemImagePath: uploaded.path,
                metadata: { ...item.metadata, imagePath: uploaded.path },
              }
            : item,
        ),
      );
    } catch (err) {
      setError(err instanceof Error ? err.message : "Per-item image upload failed");
      setAudioItems((prev) =>
        prev.map((item) => {
          if (item.id !== itemId) return item;
          if (item.itemImagePreview) URL.revokeObjectURL(item.itemImagePreview);
          return {
            ...item,
            itemImagePath: null,
            itemImageName: null,
            itemImagePreview: null,
            metadata: { ...item.metadata, imagePath: null },
          };
        }),
      );
    }
  }

  async function handleLogoUpload(file: File) {
    const preview = URL.createObjectURL(file);
    if (logoPreviewUrl) URL.revokeObjectURL(logoPreviewUrl);
    setLogoPreviewUrl(preview);
    const uploaded = await uploadFile(file, "logo");
    return uploaded.path;
  }

  async function handleFontUpload(file: File) {
    const uploaded = await uploadFile(file, "font");
    return uploaded.path;
  }

  async function handleBackgroundUpload(file: File) {
    const preview = URL.createObjectURL(file);
    if (backgroundPreviewUrl) URL.revokeObjectURL(backgroundPreviewUrl);
    setBackgroundPreviewUrl(preview);
    const uploaded = await uploadFile(file, "image");
    setBackgroundImagePath(uploaded.path);
    return uploaded.path;
  }

  function clearBackgroundImage() {
    if (backgroundPreviewUrl) URL.revokeObjectURL(backgroundPreviewUrl);
    setBackgroundPreviewUrl(null);
    setBackgroundImagePath(null);
  }

  function applyWatermarkToAll(next: WatermarkSettings) {
    const normalized = { ...DEFAULT_WATERMARK, ...next };
    setJobWatermark(normalized);
    setAudioItems((prev) =>
      prev.map((item) => ({
        ...item,
        metadata: {
          ...item.metadata,
          includeWatermark: normalized.mode !== "none",
          watermark: { ...normalized },
        },
      })),
    );
  }

  function applyLayoutToAll(next: LayoutSettings) {
    const normalized = { ...DEFAULT_LAYOUT, ...next };
    setJobLayout(normalized);
    if (!isLowerCornerTemplate(normalized.template)) {
      clearBackgroundImage();
    }
    setAudioItems((prev) =>
      prev.map((item) => ({
        ...item,
        metadata: {
          ...item.metadata,
          layout: { ...normalized },
        },
      })),
    );
  }

  async function handleAudioChange(e: React.ChangeEvent<HTMLInputElement>) {
    const files = Array.from(e.target.files || []);
    if (!files.length) return;
    setError(null);

    const maxBatch = quota?.maxBatchSize ?? 100;
    if (audioItems.length + files.length > maxBatch) {
      setError(`You can upload up to ${maxBatch} audio files per batch.`);
      e.target.value = "";
      return;
    }

    await uploadFilesWithConcurrency(files);
    e.target.value = "";
  }

  function updateItemMetadata(id: string, updates: Partial<ItemMetadata>) {
    setAudioItems((prev) =>
      prev.map((item) =>
        item.id === id
          ? { ...item, metadata: { ...item.metadata, ...updates } }
          : item,
      ),
    );
  }

  function handlePlaylistChange(nextPlaylistId: string) {
    setPlaylistId(nextPlaylistId);
    setAudioItems((prev) =>
      prev.map((item) => ({
        ...item,
        metadata: {
          ...item.metadata,
          playlistId: nextPlaylistId || null,
        },
      })),
    );
  }

  function removeAudioItem(id: string) {
    setAudioItems((prev) => prev.filter((item) => item.id !== id));
  }

  const readyAudios = audioItems.filter((a) => a.path && !a.uploading);
  const canSubmit =
    imagePath &&
    !imageUploading &&
    readyAudios.length > 0 &&
    readyAudios.every((a) =>
      Boolean(
        resolveYouTubeTitle(
          {
            title: a.metadata.title,
            songTitle: a.metadata.songTitle,
            artist: a.metadata.artist,
          },
        ),
      ),
    ) &&
    !submitting;

  async function submitJob() {
    if (!canSubmit || !imagePath) return;

    setSubmitting(true);
    setError(null);

    try {
      const effectiveWatermark: WatermarkSettings = jobWatermark;

      const res = await fetch("/api/jobs", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          imagePath,
          items: readyAudios.map((item) => ({
            audioPath: item.path!,
            audioFilename: item.file.name,
            metadata: {
              ...item.metadata,
              imagePath: item.itemImagePath || item.metadata.imagePath || null,
              includeWatermark: effectiveWatermark.mode !== "none",
              watermark: effectiveWatermark,
              layout: jobLayout,
              backgroundImagePath:
                isLowerCornerTemplate(jobLayout.template) && backgroundImagePath
                  ? backgroundImagePath
                  : null,
            },
          })),
        }),
      });

      const data = await res.json();
      if (!res.ok) {
        throw new Error(data.error || "Failed to create job");
      }

      router.push(`/jobs/${data.jobId}`);
    } catch (err) {
      setError(err instanceof Error ? err.message : "Submission failed");
      setSubmitting(false);
    }
  }

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    if (!canSubmit || !imagePath) return;

    await submitJob();
  }

  return (
    <form onSubmit={handleSubmit} className="space-y-6">
      {quota && (
        <div className="rounded border border-gray-700 bg-surface-light px-4 py-3 text-sm text-gray-300">
          Self-hosted · {quota.used} videos created · up to {quota.maxBatchSize} files per batch
        </div>
      )}

      {error && (
        <div className="rounded border border-red-500/50 bg-red-500/10 px-4 py-3 text-sm text-red-300">
          {error}
        </div>
      )}

      <section className="rounded-lg border border-gray-700 bg-surface p-6 space-y-4">
        <h2 className="text-lg font-medium text-white">Files</h2>

        <div className="grid gap-4 md:grid-cols-2">
          <div>
            <label className="mb-2 block text-sm text-gray-400">Image</label>
            <input
              type="file"
              accept="image/*"
              onChange={handleImageChange}
              className="block w-full text-sm text-gray-300 file:mr-4 file:rounded file:border-0 file:bg-accent file:px-4 file:py-2 file:text-sm file:text-white"
            />
            <div className="mt-2 flex items-center gap-2 text-sm">
              <StatusDot ok={!!imagePath && !imageUploading} />
              <span className="text-gray-400">
                {imageUploading ? "Uploading…" : imageFile?.name || "No image selected"}
              </span>
            </div>
            <p className="mt-2 text-xs text-gray-500">
              Shared cover for all tracks. You can override it per audio below.
            </p>
          </div>

          <div>
            <label className="mb-2 block text-sm text-gray-400">Audio files</label>
            <input
              type="file"
              accept="audio/mpeg,audio/wav,audio/flac,.mp3,.wav,.flac"
              multiple
              onChange={handleAudioChange}
              className="block w-full text-sm text-gray-300 file:mr-4 file:rounded file:border-0 file:bg-accent file:px-4 file:py-2 file:text-sm file:text-white"
            />
            <div className="mt-2 flex items-center gap-2 text-sm">
              <StatusDot ok={readyAudios.length > 0} />
              <span className="text-gray-400">
                {audioItems.length === 0
                  ? "No audio files selected"
                  : `${readyAudios.length} of ${audioItems.length} ready`}
              </span>
            </div>
          </div>
        </div>
      </section>

      <LayoutStudio
        locked={false}
        previewImageUrl={
          audioItems.find((a) => a.itemImagePreview)?.itemImagePreview || imagePreviewUrl
        }
        previewBackgroundUrl={backgroundPreviewUrl}
        songTitle={
          audioItems[0]?.metadata.songTitle ||
          audioItems[0]?.metadata.title ||
          "Track title"
        }
        artist={audioItems[0]?.metadata.artist || ""}
        encodeWidth={parseInt(readyAudios[0]?.metadata.resolution?.split("x")[0] || "1280", 10)}
        layout={jobLayout}
        onLayoutChange={applyLayoutToAll}
        watermark={jobWatermark}
        onWatermarkChange={applyWatermarkToAll}
        onUploadLogo={handleLogoUpload}
        onUploadFont={handleFontUpload}
        onUploadBackground={handleBackgroundUpload}
        onClearBackground={clearBackgroundImage}
        hasCustomBackground={Boolean(backgroundImagePath)}
        logoPreviewUrl={logoPreviewUrl}
      />

      {audioItems.length > 0 && (
        <section className="space-y-4">
          <h2 className="text-lg font-medium text-white">Video details (per audio)</h2>
          <p className="text-sm text-gray-400">
            Each audio file gets its own metadata. Video title is used for YouTube; song title and
            artist appear in art-track layouts.
          </p>

          <div className="rounded-lg border border-gray-700 bg-surface p-6">
            <PlaylistSelect
              value={playlistId}
              onChange={handlePlaylistChange}
              enabled
            />
          </div>

          {audioItems.map((item, index) => (
            <div
              key={item.id}
              className="rounded-lg border border-gray-700 bg-surface p-6 space-y-4"
            >
              <div className="flex items-center justify-between">
                <div>
                  <h3 className="font-medium text-white">
                    Video {index + 1}: {item.file.name}
                  </h3>
                  {item.uploading && (
                    <p className="text-xs text-yellow-400">Uploading…</p>
                  )}
                </div>
                <button
                  type="button"
                  onClick={() => removeAudioItem(item.id)}
                  className="text-sm text-red-400 hover:text-red-300"
                >
                  Remove
                </button>
              </div>

              <div className="grid gap-4 md:grid-cols-2">
                <Field label="Video title (YouTube)">
                  <input
                    type="text"
                    value={item.metadata.title}
                    onChange={(e) => updateItemMetadata(item.id, { title: e.target.value })}
                    className="input-field"
                    required
                    placeholder="Title shown on YouTube"
                  />
                </Field>

                <Field label="Song title (on-video)">
                  <input
                    type="text"
                    value={item.metadata.songTitle ?? ""}
                    onChange={(e) => updateItemMetadata(item.id, { songTitle: e.target.value })}
                    className="input-field"
                    placeholder="Shown in art-track layout"
                    maxLength={SONG_TITLE_MAX}
                  />
                </Field>
              </div>

              <div className="grid gap-4 md:grid-cols-2">
                <Field label="Artist (on-video)">
                  <input
                    type="text"
                    value={item.metadata.artist ?? ""}
                    onChange={(e) => updateItemMetadata(item.id, { artist: e.target.value })}
                    className="input-field"
                    placeholder="Shown in art-track layout"
                    maxLength={80}
                  />
                </Field>
                <Field label="Category">
                  <CategorySelect
                    value={item.metadata.categoryId}
                    onChange={(v) => updateItemMetadata(item.id, { categoryId: v })}
                  />
                </Field>
              </div>

              <Field label="Description">
                <textarea
                  value={item.metadata.description}
                  onChange={(e) => updateItemMetadata(item.id, { description: e.target.value })}
                  rows={3}
                  className="input-field resize-y"
                />
              </Field>

              <Field label="Tags">
                <input
                  type="text"
                  value={item.metadata.tags}
                  onChange={(e) => updateItemMetadata(item.id, { tags: e.target.value })}
                  placeholder='Separate with spaces or commas. Use "quoted phrases" for multi-word tags.'
                  className="input-field"
                />
              </Field>

              <Field label="Privacy">
                <PrivacyToggle
                  value={item.metadata.privacy}
                  onChange={(v) => updateItemMetadata(item.id, { privacy: v })}
                />
              </Field>

              <Field label="Video size">
                <ResolutionSelect
                  value={item.metadata.resolution}
                  onChange={(v) => updateItemMetadata(item.id, { resolution: v })}
                />
              </Field>

              <div className="relative rounded border border-gray-800 bg-surface-light/40 p-3">
                <Field label="Cover for this track (optional)">
                  <input
                    type="file"
                    accept="image/*"
                    onChange={(e) => {
                      const f = e.target.files?.[0] ?? null;
                      void handleItemImageChange(item.id, f);
                      e.target.value = "";
                    }}
                    className="block w-full text-sm text-gray-300 file:mr-4 file:rounded file:border-0 file:bg-accent file:px-4 file:py-2 file:text-sm file:text-white disabled:opacity-50"
                  />
                </Field>
                {item.itemImageName && (
                  <p className="mt-1 text-xs text-gray-500">
                    {item.itemImagePath ? `Using ${item.itemImageName}` : `Uploading ${item.itemImageName}…`}
                  </p>
                )}
              </div>

              <div className="grid gap-3 sm:grid-cols-2">
                <Checkbox
                  label="Notify subscribers about this upload"
                  checked={item.metadata.notifySubscribers}
                  onChange={(v) => updateItemMetadata(item.id, { notifySubscribers: v })}
                />
                <Checkbox
                  label="Made For Kids?"
                  checked={item.metadata.madeForKids}
                  onChange={(v) => updateItemMetadata(item.id, { madeForKids: v })}
                />
                <Checkbox
                  label="Embeddable?"
                  checked={item.metadata.embeddable}
                  onChange={(v) => updateItemMetadata(item.id, { embeddable: v })}
                />
                <Checkbox
                  label="Creative Commons?"
                  checked={item.metadata.creativeCommons}
                  onChange={(v) => updateItemMetadata(item.id, { creativeCommons: v })}
                />
              </div>
            </div>
          ))}
        </section>
      )}

      <button
        type="submit"
        disabled={!canSubmit}
        className="w-full rounded bg-accent px-6 py-3 font-medium text-white transition-colors hover:bg-accent-hover disabled:cursor-not-allowed disabled:opacity-50"
      >
        {submitting
          ? "Creating videos…"
          : `Create ${readyAudios.length || ""} Video${readyAudios.length !== 1 ? "s" : ""}`}
      </button>
    </form>
  );
}

function StatusDot({ ok }: { ok: boolean }) {
  return (
    <span
      className={`inline-block h-3 w-3 rounded-full ${ok ? "bg-green-500" : "bg-red-500"}`}
    />
  );
}

function Field({ label, children }: { label: string; children: React.ReactNode }) {
  return (
    <div>
      <label className="mb-1 block text-sm text-gray-400">{label}</label>
      {children}
    </div>
  );
}

function Checkbox({
  label,
  checked,
  onChange,
  disabled,
}: {
  label: string;
  checked: boolean;
  onChange: (v: boolean) => void;
  disabled?: boolean;
}) {
  return (
    <label className={`flex items-center gap-2 text-sm text-gray-300 ${disabled ? "opacity-60" : ""}`}>
      <input
        type="checkbox"
        checked={checked}
        onChange={(e) => onChange(e.target.checked)}
        disabled={disabled}
        className="rounded border-gray-600 bg-surface-light text-accent focus:ring-accent"
      />
      {label}
    </label>
  );
}
