"use client";

import { useCallback, useEffect, useRef, useState } from "react";
import { useRouter } from "next/navigation";
import { Plan, Privacy } from "@prisma/client";
import { filenameWithoutExtension } from "@/lib/constants";
import { audioTagsToMetadata } from "@/lib/audio-tags";
import type { ItemMetadata } from "@/lib/types";
import { CategorySelect } from "./CategorySelect";
import { PlaylistSelect } from "./PlaylistSelect";
import { PrivacyToggle } from "./PrivacyToggle";
import { ResolutionSelect } from "./ResolutionSelect";
import { QuotaErrorMessage, UpgradeProLink } from "./UpgradeProLink";

const UPLOAD_CONCURRENCY = 4;

type AudioItem = {
  id: string;
  file: File;
  path: string | null;
  uploading: boolean;
  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,
  };
}

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 [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 [quota, setQuota] = useState<{
    remaining: number;
    limit: number;
    plan: Plan;
    maxBatchSize: number;
    resetsIn: string;
    videoCredits: number;
    totalAvailable: number;
    used: number;
    selfHosted?: boolean;
  } | null>(null);

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

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

  async function uploadFile(
    file: File,
    type: "image" | "audio",
  ): 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 });
    if (!res.ok) {
      const data = await res.json();
      throw new Error(data.error || "Upload failed");
    }
    const data = await res.json();
    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,
          metadata: {
            ...defaultMetadata(autoTitle),
            playlistId: playlistId || null,
          },
        },
      ]);

      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);
    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);
    } finally {
      setImageUploading(false);
    }
  }

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

    const maxBatch = quota?.maxBatchSize ?? 3;
    if (audioItems.length + files.length > maxBatch) {
      setError(`Your plan allows 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) => a.metadata.title.trim()) &&
    !submitting;

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

    setSubmitting(true);
    setError(null);

    try {
      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,
          })),
        }),
      });

      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);
    }
  }

  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">
          {quota.selfHosted ? (
            <>
              Self-hosted · {quota.used} videos processed · up to {quota.maxBatchSize} files per
              batch
            </>
          ) : (
            <>
              {quota.remaining} of {quota.limit} plan videos remaining
              {quota.plan === "FREE" && quota.videoCredits > 0
                ? ` · ${quota.videoCredits} pay-as-you-go credits`
                : ""}{" "}
              ·{" "}
              {quota.plan === "FREE"
                ? `resets in ${quota.resetsIn}`
                : `monthly quota resets on ${quota.resetsIn}`}{" "}
              · 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">
          <QuotaErrorMessage message={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>
          </div>

          <div>
            <label className="mb-2 block text-sm text-gray-400">Audio files</label>
            <input
              type="file"
              accept={
                quota?.selfHosted || quota?.plan === "PREMIUM"
                  ? "audio/mpeg,audio/wav,audio/flac,.mp3,.wav,.flac"
                  : "audio/mpeg,.mp3"
              }
              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>

      {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. Title is auto-filled from the filename.
          </p>

          <div className="rounded-lg border border-gray-700 bg-surface p-6">
            <PlaylistSelect
              value={playlistId}
              onChange={handlePlaylistChange}
              enabled={Boolean(quota?.selfHosted || quota?.plan === "PREMIUM")}
            />
          </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="Title">
                  <input
                    type="text"
                    value={item.metadata.title}
                    onChange={(e) => updateItemMetadata(item.id, { title: e.target.value })}
                    className="input-field"
                    required
                  />
                </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 })}
                  plan={quota?.plan}
                />
              </Field>

              <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 })}
                />
                <Checkbox
                  label="Show 'Uploaded through Songs2YT.com' watermark (support open-source)"
                  checked={item.metadata.includeWatermark}
                  onChange={(v) => updateItemMetadata(item.id, { includeWatermark: v })}
                />
              </div>
            </div>
          ))}
        </section>
      )}

      {quota && !quota.selfHosted && quota.plan === "FREE" && (
        <>
          <p className="text-sm text-gray-400">
            Watermark is optional on the free plan - turn it off anytime for a clean video.
          </p>
          <p className="text-sm text-gray-400">
            <UpgradeProLink className="text-accent hover:underline" /> for 50 videos/month, 1080p,
            lossless audio, and full watermark control.
          </p>
        </>
      )}

      <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>
  );
}
