"use client";

import { useCallback, useEffect, useState, type ChangeEvent, type FormEvent } from "react";
import Image from "next/image";
import { Avatar } from "@/components/avatar";
import { IconCamera, IconClose } from "@/components/icons";
import { EmptyState } from "@/components/empty-state";
import { resizeImageToBlob } from "@/lib/image";
import { createClient, waitForRealtimeAuth } from "@/lib/supabase/client";
import type { MyReactionRow, ReactionCountRow, WallHistoryRow, WallNoteRow } from "./page";

const MAX_LENGTH = 200;
// Les Archontes peuvent désormais publier aussi (plus réservé aux
// Citoyens) : 12 places pour couvrir tout le groupe, pas seulement les
// Citoyens — doit rester aligné avec la borne côté serveur dans
// post_wall_note (supabase/schema.sql).
const MAX_NOTES_PER_DAY = 12;
const MAX_IMAGE_SOURCE_BYTES = 20 * 1024 * 1024;
const REACTION_EMOJIS = ["👍", "😂", "😱", "❤️", "🔥"];

// Rotation déterministe par note (jamais Math.random() au rendu, cf. la
// même contrainte déjà rencontrée cette session avec les hooks React) —
// donne l'effet "punaisé en vrac" sans risquer un hydration mismatch.
function rotationFor(id: string): number {
  let hash = 0;
  for (let i = 0; i < id.length; i++) hash = (hash * 31 + id.charCodeAt(i)) | 0;
  return (Math.abs(hash) % 7) - 3;
}

function formatDateTime(iso: string): string {
  return new Date(iso).toLocaleString("fr-FR", {
    dateStyle: "short",
    timeStyle: "short",
    timeZone: "Europe/Paris",
  });
}

// Clé de date locale (Europe/Paris) au format sv-SE (YYYY-MM-DD, triable),
// même pattern que calendrier/day-card.tsx.
function localDateKey(iso: string): string {
  return new Date(iso).toLocaleDateString("sv-SE", { timeZone: "Europe/Paris" });
}

function formatDateLabel(dateKey: string): string {
  const label = new Date(`${dateKey}T12:00:00`).toLocaleDateString("fr-FR", {
    weekday: "long",
    day: "numeric",
    month: "long",
    timeZone: "Europe/Paris",
  });
  return label.charAt(0).toUpperCase() + label.slice(1);
}

function buildReactionCounts(rows: ReactionCountRow[]): Record<string, Record<string, number>> {
  const map: Record<string, Record<string, number>> = {};
  for (const row of rows) {
    if (!map[row.note_id]) map[row.note_id] = {};
    map[row.note_id][row.emoji] = row.count;
  }
  return map;
}

function buildMyReactions(rows: MyReactionRow[]): Record<string, Set<string>> {
  const map: Record<string, Set<string>> = {};
  for (const row of rows) {
    if (!map[row.note_id]) map[row.note_id] = new Set();
    map[row.note_id].add(row.emoji);
  }
  return map;
}

function ReactionBar({
  counts,
  mine,
  onToggle,
}: {
  counts: Record<string, number>;
  mine: Set<string>;
  onToggle: (emoji: string) => void;
}) {
  return (
    <div className="flex flex-wrap gap-1">
      {REACTION_EMOJIS.map((emoji) => {
        const count = counts[emoji] ?? 0;
        const active = mine.has(emoji);
        return (
          <button
            key={emoji}
            type="button"
            onClick={() => onToggle(emoji)}
            className={`flex items-center gap-0.5 rounded-full border px-1.5 py-0.5 text-xs transition ${
              active ? "border-gold bg-gold/20" : "border-gold/20 hover:bg-gold/10"
            }`}
          >
            <span>{emoji}</span>
            {count > 0 && <span className="text-[0.6rem] text-text-mut">{count}</span>}
          </button>
        );
      })}
    </div>
  );
}

function NoteCard({
  note,
  reactionCounts,
  myReactions,
  onToggleReaction,
  onImageClick,
}: {
  note: WallNoteRow;
  reactionCounts: Record<string, number>;
  myReactions: Set<string>;
  onToggleReaction: (emoji: string) => void;
  onImageClick: (url: string) => void;
}) {
  const imageUrl = note.image_url;

  return (
    <div
      className="marble-surface flex min-h-[7rem] flex-col justify-between gap-2 rounded-lg border border-gold/30 p-3 shadow-md"
      style={{ transform: `rotate(${rotationFor(note.id)}deg)` }}
    >
      <div className="flex flex-col gap-2">
        {imageUrl && (
          <button
            type="button"
            onClick={() => onImageClick(imageUrl)}
            className="cursor-zoom-in"
            aria-label="Agrandir l'image"
          >
            <Image src={imageUrl} alt="" width={300} height={200} className="max-h-32 w-full rounded-md object-cover" />
          </button>
        )}
        <p className="text-sm text-text-marble">{note.text}</p>
      </div>
      <div className="flex items-center justify-between gap-2">
        <ReactionBar counts={reactionCounts} mine={myReactions} onToggle={onToggleReaction} />
        <span className="h-2 w-2 shrink-0 rounded-full bg-oxblood" aria-hidden />
      </div>
    </div>
  );
}

export function MurView({
  isJudge,
  currentUserId,
  initialNotes,
  initialMyNoteToday,
  initialHistory,
  initialReactionCounts,
  initialMyReactions,
  availableDays,
}: {
  isJudge: boolean;
  currentUserId: string;
  initialNotes: WallNoteRow[];
  initialMyNoteToday: WallNoteRow | null;
  initialHistory: WallHistoryRow[];
  initialReactionCounts: ReactionCountRow[];
  initialMyReactions: MyReactionRow[];
  availableDays: string[];
}) {
  const [notes, setNotes] = useState(initialNotes);
  const [myNoteToday, setMyNoteToday] = useState(initialMyNoteToday);
  const [history, setHistory] = useState(initialHistory);
  const [reactionCounts, setReactionCounts] = useState(() => buildReactionCounts(initialReactionCounts));
  const [myReactions, setMyReactions] = useState(() => buildMyReactions(initialMyReactions));
  const [text, setText] = useState("");
  const [imageFile, setImageFile] = useState<File | null>(null);
  const [imagePreview, setImagePreview] = useState<string | null>(null);
  const [imageError, setImageError] = useState<string | null>(null);
  const [posting, setPosting] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [historyDateFilter, setHistoryDateFilter] = useState("all");
  const [zoomedImage, setZoomedImage] = useState<string | null>(null);
  // Jour affiché dans la grille du haut (ouverte à tout le monde,
  // contrairement à l'historique complet réservé aux Archontes plus bas) :
  // null = aujourd'hui, réactif au Realtime comme avant. Un jour passé se
  // charge à la demande (pas de Realtime dessus, un jour passé n'évolue
  // plus) dans un état séparé pour ne jamais mélanger les deux.
  const [viewedDate, setViewedDate] = useState<string | null>(null);
  const [pastNotes, setPastNotes] = useState<WallNoteRow[]>([]);
  const [pastReactionCounts, setPastReactionCounts] = useState<Record<string, Record<string, number>>>({});
  const [pastLoading, setPastLoading] = useState(false);

  const refetch = useCallback(async () => {
    const supabase = createClient();
    const [{ data: todayNotes }, { data: myNotes }, { data: hist }, { data: counts }, { data: mine }] =
      await Promise.all([
        supabase.rpc("wall_notes_today"),
        // Depuis V14, les Archontes peuvent aussi publier — plus de
        // filtre par rôle ici non plus (sinon leur note du jour ne se
        // mettait jamais à jour après le chargement initial).
        supabase
          .from("wall_notes")
          .select("id, text, image_url, created_at")
          .order("created_at", { ascending: false }),
        isJudge
          ? supabase
              .from("wall_notes")
              .select("id, user_id, text, image_url, created_at, profiles(pseudo, avatar_url)")
              .order("created_at", { ascending: false })
              .returns<WallHistoryRow[]>()
          : Promise.resolve({ data: null }),
        supabase.rpc("wall_note_reaction_counts"),
        supabase.from("wall_note_reactions").select("note_id, emoji").eq("user_id", currentUserId),
      ]);
    if (todayNotes) setNotes(todayNotes);
    if (myNotes) {
      const today = new Date().toLocaleDateString("sv-SE", { timeZone: "Europe/Paris" });
      setMyNoteToday(
        myNotes.find(
          (n) => new Date(n.created_at).toLocaleDateString("sv-SE", { timeZone: "Europe/Paris" }) === today,
        ) ?? null,
      );
    }
    if (hist) setHistory(hist);
    if (counts) setReactionCounts(buildReactionCounts(counts));
    if (mine) setMyReactions(buildMyReactions(mine));
  }, [isJudge, currentUserId]);

  useEffect(() => {
    const supabase = createClient();
    let channel: ReturnType<typeof supabase.channel> | null = null;
    let cancelled = false;

    waitForRealtimeAuth(supabase).then(() => {
      if (cancelled) return;
      channel = supabase
        .channel("wall-notes-changes")
        .on("postgres_changes", { event: "*", schema: "public", table: "wall_notes" }, refetch)
        .on("postgres_changes", { event: "*", schema: "public", table: "wall_note_reactions" }, refetch)
        .subscribe();
    });

    return () => {
      cancelled = true;
      if (channel) supabase.removeChannel(channel);
    };
  }, [refetch]);

  const fetchDay = useCallback(async (dateKey: string) => {
    const supabase = createClient();
    const [{ data: dayNotes }, { data: dayCounts }] = await Promise.all([
      supabase.rpc("wall_notes_for_date", { p_date: dateKey }),
      supabase.rpc("wall_note_reaction_counts", { p_date: dateKey }),
    ]);
    setPastNotes(dayNotes ?? []);
    setPastReactionCounts(buildReactionCounts(dayCounts ?? []));
  }, []);

  async function handleSelectDate(value: string) {
    if (value === "today") {
      setViewedDate(null);
      return;
    }
    setViewedDate(value);
    setPastLoading(true);
    await fetchDay(value);
    setPastLoading(false);
  }

  function handleImageChange(event: ChangeEvent<HTMLInputElement>) {
    const file = event.target.files?.[0];
    event.target.value = "";
    if (!file) return;

    if (!file.type.startsWith("image/")) {
      setImageError("Choisis une image.");
      return;
    }
    if (file.size > MAX_IMAGE_SOURCE_BYTES) {
      setImageError("Image trop lourde, choisis-en une autre.");
      return;
    }

    setImageError(null);
    setImageFile(file);
    setImagePreview((current) => {
      if (current) URL.revokeObjectURL(current);
      return URL.createObjectURL(file);
    });
  }

  function clearImage() {
    setImageFile(null);
    setImagePreview((current) => {
      if (current) URL.revokeObjectURL(current);
      return null;
    });
    setImageError(null);
  }

  async function handleSubmit(event: FormEvent) {
    event.preventDefault();
    setError(null);

    const trimmed = text.trim();
    if (!trimmed) {
      setError("Écris une note avant de publier.");
      return;
    }

    setPosting(true);
    const supabase = createClient();

    let imageUrl: string | null = null;
    if (imageFile) {
      try {
        const blob = await resizeImageToBlob(imageFile);
        // L'extension/le Content-Type suivent le type RÉEL du blob rendu par
        // canvas.toBlob (pas "image/webp" en dur) : certains navigateurs
        // (Safari notamment, webp encodé via canvas longtemps mal supporté)
        // retombent silencieusement sur du PNG malgré le type demandé —
        // uploader ce PNG sous une étiquette "webp" trompeuse pouvait faire
        // échouer des vérifications en aval qui, elles, regardent le vrai
        // contenu (l'optimiseur d'image de next/image par exemple).
        const extension = blob.type === "image/png" ? "png" : blob.type === "image/jpeg" ? "jpg" : "webp";
        const path = `${crypto.randomUUID()}.${extension}`;
        const { error: uploadError } = await supabase.storage
          .from("wall-images")
          .upload(path, blob, { contentType: blob.type || "image/webp" });
        if (uploadError) {
          setPosting(false);
          console.error("Échec de l'envoi de l'image sur le Mur :", uploadError);
          setError(`Envoi de l'image impossible (${uploadError.message}). Réessaie.`);
          return;
        }
        const { data: publicUrlData } = supabase.storage.from("wall-images").getPublicUrl(path);
        imageUrl = publicUrlData.publicUrl;
      } catch (err) {
        setPosting(false);
        console.error("Échec du traitement de l'image sur le Mur :", err);
        setError("Traitement de l'image impossible (format non supporté ?) — réessaie sans image ou avec une autre photo.");
        return;
      }
    }

    const { error: rpcError } = await supabase.rpc("post_wall_note", {
      p_text: trimmed,
      p_image_url: imageUrl,
    });
    setPosting(false);

    if (rpcError) {
      console.error("Échec de post_wall_note :", rpcError);
      setError(
        rpcError.message.includes("already posted")
          ? "Tu as déjà publié une note aujourd'hui."
          : rpcError.message.includes("wall full")
            ? "Le mur est déjà complet pour aujourd'hui."
            : rpcError.message.includes("image invalide")
              ? "L'image n'a pas pu être validée côté serveur — réessaie sans image ou avec une autre photo."
              : `Publication impossible (${rpcError.message}). Réessaie.`,
      );
      refetch();
      return;
    }

    setText("");
    clearImage();
    refetch();
  }

  async function handleToggleReaction(noteId: string, emoji: string) {
    // Optimiste : le canal Realtime confirmera (ou corrigera) juste après —
    // sauf en dehors d'aujourd'hui, où il n'y a pas de Realtime (un jour
    // passé n'évolue plus) : on corrige alors en rechargeant ce jour-là.
    const setCounts = viewedDate === null ? setReactionCounts : setPastReactionCounts;

    setMyReactions((current) => {
      const next = new Set(current[noteId] ?? []);
      const wasActive = next.has(emoji);
      if (wasActive) next.delete(emoji);
      else next.add(emoji);
      return { ...current, [noteId]: next };
    });
    setCounts((current) => {
      const wasActive = myReactions[noteId]?.has(emoji) ?? false;
      const noteCounts = { ...(current[noteId] ?? {}) };
      noteCounts[emoji] = Math.max(0, (noteCounts[emoji] ?? 0) + (wasActive ? -1 : 1));
      return { ...current, [noteId]: noteCounts };
    });

    const supabase = createClient();
    const { error: rpcError } = await supabase.rpc("toggle_wall_note_reaction", {
      p_note_id: noteId,
      p_emoji: emoji,
    });
    if (rpcError) {
      if (viewedDate === null) refetch();
      else fetchDay(viewedDate);
    }
  }

  // wallFull/le compteur restent toujours basés sur aujourd'hui (notes),
  // jamais sur le jour affiché (displayedNotes) : consulter un jour passé
  // ne doit jamais changer l'éligibilité à publier aujourd'hui.
  const wallFull = notes.length >= MAX_NOTES_PER_DAY;

  const todayKey = new Date().toLocaleDateString("sv-SE", { timeZone: "Europe/Paris" });
  const pastDays = availableDays.filter((day) => day !== todayKey);
  const displayedNotes = viewedDate === null ? notes : pastNotes;
  const displayedReactionCounts = viewedDate === null ? reactionCounts : pastReactionCounts;

  const historyDates = Array.from(new Set(history.map((n) => localDateKey(n.created_at)))).sort((a, b) =>
    b.localeCompare(a),
  );
  const filteredHistory =
    historyDateFilter === "all" ? history : history.filter((n) => localDateKey(n.created_at) === historyDateFilter);

  return (
    <div className="flex flex-col gap-6">
      <div>
        <div className="mb-3 flex flex-wrap items-center justify-between gap-2">
          <p className="text-xs font-medium tracking-wide text-marble/60 uppercase">
            {viewedDate === null ? `${notes.length} / ${MAX_NOTES_PER_DAY} aujourd'hui` : formatDateLabel(viewedDate)}
          </p>
          {pastDays.length > 0 && (
            <select
              value={viewedDate ?? "today"}
              onChange={(event) => handleSelectDate(event.target.value)}
              className="rounded-md border border-gold/30 bg-white/50 px-2 py-1 text-xs text-text-marble outline-none focus:border-gold"
            >
              <option value="today">Aujourd&apos;hui</option>
              {pastDays.map((day) => (
                <option key={day} value={day}>
                  {formatDateLabel(day)}
                </option>
              ))}
            </select>
          )}
        </div>
        {pastLoading ? (
          <p className="text-center text-sm text-marble/60">Chargement…</p>
        ) : displayedNotes.length === 0 ? (
          <EmptyState message={viewedDate === null ? "Le mur est encore vide aujourd'hui." : "Le mur était vide ce jour-là."} />
        ) : (
          <div className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4">
            {displayedNotes.map((note) => (
              <NoteCard
                key={note.id}
                note={note}
                reactionCounts={displayedReactionCounts[note.id] ?? {}}
                myReactions={myReactions[note.id] ?? new Set()}
                onToggleReaction={(emoji) => handleToggleReaction(note.id, emoji)}
                onImageClick={setZoomedImage}
              />
            ))}
          </div>
        )}
      </div>

      <div className="marble-surface rounded-2xl border border-gold/40 p-4 shadow-sm">
        {myNoteToday ? (
          <div>
            <p className="text-sm text-olive">Ta note d&apos;aujourd&apos;hui est publiée.</p>
            {myNoteToday.image_url && (
              <button
                type="button"
                onClick={() => setZoomedImage(myNoteToday.image_url)}
                className="cursor-zoom-in"
                aria-label="Agrandir l'image"
              >
                <Image
                  src={myNoteToday.image_url}
                  alt=""
                  width={300}
                  height={200}
                  className="mt-2 max-h-40 w-full rounded-md object-cover"
                />
              </button>
            )}
            <p className="mt-2 rounded-md border border-gold/20 bg-white/40 px-3 py-2 text-sm text-text-marble">
              {myNoteToday.text}
            </p>
            <p className="mt-2 text-xs text-text-mut">Reviens demain pour en publier une nouvelle.</p>
          </div>
        ) : wallFull ? (
          <p className="text-sm text-text-mut">Le mur est complet pour aujourd&apos;hui, reviens demain !</p>
        ) : (
          <form onSubmit={handleSubmit} className="flex flex-col gap-2">
            <textarea
              value={text}
              onChange={(event) => setText(event.target.value)}
              maxLength={MAX_LENGTH}
              rows={2}
              placeholder="Écrire une note anonyme…"
              className="rounded-md border border-gold/30 bg-white/50 px-3 py-2 text-sm text-text-marble outline-none focus:border-gold"
            />
            <p className="text-right text-[0.65rem] text-text-mut">
              {text.length}/{MAX_LENGTH}
            </p>

            {imagePreview ? (
              <div className="relative w-32">
                <Image
                  src={imagePreview}
                  alt=""
                  width={128}
                  height={96}
                  unoptimized
                  className="h-24 w-32 rounded-md object-cover"
                />
                <button
                  type="button"
                  onClick={clearImage}
                  className="absolute -top-2 -right-2 rounded-full bg-ink-2 p-1 text-marble"
                  aria-label="Retirer l'image"
                >
                  <IconClose className="h-3 w-3" />
                </button>
              </div>
            ) : (
              <label className="inline-flex w-fit cursor-pointer items-center gap-1.5 rounded-md border border-gold/30 px-3 py-1.5 text-xs font-medium text-text-marble hover:bg-gold/10">
                <IconCamera className="h-4 w-4" />
                Ajouter une image (optionnel)
                <input type="file" accept="image/*" onChange={handleImageChange} className="hidden" />
              </label>
            )}
            {imageError && <p className="text-xs text-oxblood">{imageError}</p>}

            {error && (
              <p role="alert" className="text-sm text-oxblood">
                {error}
              </p>
            )}
            <button
              type="submit"
              disabled={posting}
              className="rounded-md bg-ink-2 px-4 py-2 font-heading text-sm tracking-wide text-gold-bright uppercase transition hover:bg-ink disabled:opacity-50"
            >
              {posting ? "Publication…" : "Publier anonymement"}
            </button>
          </form>
        )}
      </div>

      {isJudge && (
        <div className="marble-surface rounded-2xl border border-gold/40 p-4 shadow-sm">
          <div className="mb-2 flex flex-wrap items-center justify-between gap-2">
            <p className="font-heading text-sm tracking-wide text-text-mut uppercase">Historique complet</p>
            {historyDates.length > 0 && (
              <select
                value={historyDateFilter}
                onChange={(event) => setHistoryDateFilter(event.target.value)}
                className="rounded-md border border-gold/30 bg-white/50 px-2 py-1 text-xs text-text-marble outline-none focus:border-gold"
              >
                <option value="all">Tous les jours</option>
                {historyDates.map((dateKey) => (
                  <option key={dateKey} value={dateKey}>
                    {formatDateLabel(dateKey)}
                  </option>
                ))}
              </select>
            )}
          </div>
          {filteredHistory.length === 0 ? (
            <EmptyState
              message={history.length === 0 ? "Aucune note publiée pour l'instant." : "Aucune note ce jour-là."}
              className="text-text-mut"
            />
          ) : (
            <ul className="flex flex-col divide-y divide-gold/10">
              {filteredHistory.map((note) => (
                <li key={note.id} className="flex items-start gap-2 py-1.5 text-sm">
                  <Avatar
                    pseudo={note.profiles?.pseudo ?? "?"}
                    avatarUrl={note.profiles?.avatar_url ?? null}
                    size="sm"
                  />
                  <div className="min-w-0 flex-1">
                    <p className="text-xs font-medium text-text-mut">
                      {note.profiles?.pseudo ?? "un Citoyen"} — {formatDateTime(note.created_at)}
                    </p>
                    <p className="text-text-marble">{note.text}</p>
                    {note.image_url && (
                      <button
                        type="button"
                        onClick={() => setZoomedImage(note.image_url)}
                        className="cursor-zoom-in"
                        aria-label="Agrandir l'image"
                      >
                        <Image
                          src={note.image_url}
                          alt=""
                          width={200}
                          height={150}
                          className="mt-1 max-h-32 rounded-md object-cover"
                        />
                      </button>
                    )}
                  </div>
                </li>
              ))}
            </ul>
          )}
        </div>
      )}

      {zoomedImage && (
        <div className="fixed inset-0 z-40 flex items-center justify-center bg-ink/90 p-4 backdrop-blur-sm">
          <button
            type="button"
            onClick={() => setZoomedImage(null)}
            className="absolute top-4 right-4 rounded-full bg-ink-2/80 p-2 text-marble transition hover:bg-ink-2"
            aria-label="Fermer"
          >
            <IconClose className="h-5 w-5" />
          </button>
          <div className="relative h-[80vh] w-full max-w-2xl">
            <Image src={zoomedImage} alt="" fill sizes="100vw" className="object-contain" />
          </div>
        </div>
      )}
    </div>
  );
}
