"use client";

import { Fragment, useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
import { CheckCircle2, Loader2, PanelRightClose, PanelRightOpen, RotateCcw, UserPlus, X } from "lucide-react";
import { toast } from "sonner";
import { Button } from "@/presentation/components/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/presentation/components/ui/tooltip";
import { AssigneeSelect } from "@/presentation/components/tickets/assignee-select";
import { useRepositories } from "@/di/repository-provider";
import { useAuth } from "@/presentation/hooks/use-auth";
import { useSupportActivity } from "@/presentation/hooks/use-support-activity";
import { useT, useLocale } from "@/shared/i18n/client";
import { formatDate, formatDateTime } from "@/shared/lib/format-date";
import { cn } from "@/shared/lib/utils";
import { displayName } from "@/shared/lib/user-display";
import { TICKET_STATUS_COLORS } from "@/shared/constants/status-colors";
import { PersonAvatar } from "./person-avatar";
import { SupportComposer } from "./support-composer";
import { ThreadMessage } from "./thread-message";
import { formatDayLabel, isSameDay, isSameGroup, parseDate } from "./support-utils";
import type {
  CreateTicketMessagePayload,
  SupportInboxCounts,
  Ticket,
  TicketDetail,
  TicketMessage,
  TicketMessageAttachment,
} from "@/domain/entities/ticket";

const NEAR_BOTTOM_PX = 160;
const DONE_STATUSES = ["Resolved", "Closed"];

interface ConversationThreadProps {
  conversationId: number;
  /** Row from the list, shown while the conversation loads. */
  summary: Ticket | null;
  panelOpen: boolean;
  onTogglePanel: () => void;
  onClose: () => void;
  /** Something changed (message, read, status, assignment): refresh the list and counts. */
  onChanged: (counts?: SupportInboxCounts) => void;
  onDetailLoaded: (detail: TicketDetail) => void;
}

export function ConversationThread({
  conversationId,
  summary,
  panelOpen,
  onTogglePanel,
  onClose,
  onChanged,
  onDetailLoaded,
}: ConversationThreadProps) {
  const t = useT();
  const locale = useLocale();
  const container = useRepositories();
  const { user, hasPermission } = useAuth();
  const canManage = hasPermission("tickets.manage");

  const [detail, setDetail] = useState<TicketDetail | null>(null);
  const [messages, setMessages] = useState<TicketMessage[]>([]);
  const [loading, setLoading] = useState(true);
  const [failed, setFailed] = useState(false);
  const [firstUnreadId, setFirstUnreadId] = useState<number | null>(null);
  const [updating, setUpdating] = useState(false);
  const [lightbox, setLightbox] = useState<TicketMessageAttachment | null>(null);
  const scroller = useRef<HTMLDivElement>(null);
  const content = useRef<HTMLDivElement>(null);
  const unreadMarker = useRef<HTMLDivElement>(null);
  const pendingScroll = useRef<"unread" | "bottom" | null>(null);
  const knownIds = useRef(new Set<number>());

  const onChangedRef = useRef(onChanged);
  onChangedRef.current = onChanged;
  const onDetailLoadedRef = useRef(onDetailLoaded);
  onDetailLoadedRef.current = onDetailLoaded;

  const fromTeam = (message: TicketMessage) => message.author?.from_dadycar ?? message.kind === "internal";

  const isNearBottom = () => {
    const el = scroller.current;
    return !el || el.scrollHeight - el.scrollTop - el.clientHeight < NEAR_BOTTOM_PX;
  };

  const markRead = useCallback(async () => {
    try {
      const counts = await container.ticketRepository.markRead(conversationId);
      onChangedRef.current(counts);
    } catch {
      // Read state is best effort.
    }
  }, [container, conversationId]);

  const refreshDetail = useCallback(async () => {
    try {
      const fresh = await container.ticketRepository.findById(conversationId);
      setDetail(fresh);
      onDetailLoadedRef.current(fresh);
      return fresh;
    } catch {
      return null;
    }
  }, [container, conversationId]);

  const mergeMessages = useCallback((incoming: TicketMessage[]) => {
    const fresh = incoming.filter((m) => !knownIds.current.has(m.id));
    if (fresh.length === 0) return [];
    fresh.forEach((m) => knownIds.current.add(m.id));
    setMessages((prev) => [...prev, ...fresh].sort((a, b) => a.id - b.id));
    return fresh;
  }, []);

  // Load the conversation, open it at the first unread client message, then mark it read.
  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    setFailed(false);
    setMessages([]);
    setDetail(null);
    setFirstUnreadId(null);
    knownIds.current = new Set();

    Promise.all([
      container.ticketRepository.findById(conversationId),
      container.ticketRepository.getMessages(conversationId),
    ])
      .then(([loadedDetail, loadedMessages]) => {
        if (cancelled) return;
        setDetail(loadedDetail);
        onDetailLoadedRef.current(loadedDetail);
        loadedMessages.forEach((m) => knownIds.current.add(m.id));
        setMessages(loadedMessages);

        const unread = loadedDetail.unread_count ?? 0;
        const fromClient = loadedMessages.filter((m) => m.kind === "client" && !m.author?.from_dadycar);
        const firstUnread = unread > 0 ? fromClient[Math.max(fromClient.length - unread, 0)] : undefined;
        setFirstUnreadId(firstUnread?.id ?? null);
        pendingScroll.current = firstUnread ? "unread" : "bottom";
        setLoading(false);
        if (unread > 0) void markRead();
      })
      .catch(() => {
        if (cancelled) return;
        setFailed(true);
        setLoading(false);
      });

    return () => { cancelled = true; };
  }, [container, conversationId, markRead]);

  useSupportActivity(async (event) => {
    if (loading) return;
    if (event && event.ticket_id !== conversationId) return;

    if (event?.reason === "message" && event.message) {
      const stick = isNearBottom();
      const added = mergeMessages([event.message]);
      if (added.length === 0) return;
      if (stick) pendingScroll.current = "bottom";
      if (added.some((m) => !fromTeam(m)) && document.visibilityState === "visible") void markRead();
      return;
    }

    if (event?.reason === "assignment" || event?.reason === "status") {
      void refreshDetail();
      return;
    }

    // Polling tick (no WebSocket): pick up anything new.
    try {
      const latest = await container.ticketRepository.getMessages(conversationId);
      const stick = isNearBottom();
      const added = mergeMessages(latest);
      if (added.length > 0) {
        if (stick) pendingScroll.current = "bottom";
        if (added.some((m) => !fromTeam(m)) && document.visibilityState === "visible") void markRead();
      }
      const fresh = await refreshDetail();
      if (fresh && (fresh.unread_count ?? 0) > 0 && document.visibilityState === "visible") void markRead();
    } catch {
      // Next tick retries.
    }
  });

  useLayoutEffect(() => {
    const el = scroller.current;
    if (!el || loading || !pendingScroll.current) return;
    if (pendingScroll.current === "unread" && unreadMarker.current) {
      el.scrollTop = Math.max(unreadMarker.current.offsetTop - 16, 0);
    } else {
      el.scrollTop = el.scrollHeight;
    }
    pendingScroll.current = null;
  }, [messages, loading]);

  // Images and audio load after the first scroll: stay at the bottom if the reader was there.
  useEffect(() => {
    const el = scroller.current;
    if (!el || !content.current || loading) return;
    let previousHeight = el.scrollHeight;
    const observer = new ResizeObserver(() => {
      if (previousHeight - el.scrollTop - el.clientHeight < NEAR_BOTTOM_PX && !pendingScroll.current) {
        el.scrollTop = el.scrollHeight;
      }
      previousHeight = el.scrollHeight;
    });
    observer.observe(content.current);
    return () => observer.disconnect();
  }, [loading]);

  const send = async (payload: CreateTicketMessagePayload) => {
    try {
      const message = await container.ticketRepository.createMessage(conversationId, payload);
      pendingScroll.current = "bottom";
      mergeMessages([message]);
      setFirstUnreadId(null);
      onChangedRef.current();
      if (payload.kind === "client") void refreshDetail();
      return true;
    } catch (error) {
      const message = (error as { response?: { data?: { message?: string } } })?.response?.data?.message;
      toast.error(message || t.support.composer.sendFailed);
      return false;
    }
  };

  const runUpdate = async (action: () => Promise<TicketDetail>, successMessage: string) => {
    setUpdating(true);
    try {
      const fresh = await action();
      setDetail((prev) => (prev ? { ...prev, ...fresh } : fresh));
      toast.success(successMessage);
      void refreshDetail();
      onChangedRef.current();
    } catch (error) {
      const message = (error as { response?: { data?: { message?: string } } })?.response?.data?.message;
      toast.error(message || t.support.loadError);
    } finally {
      setUpdating(false);
    }
  };

  const current = detail ?? summary;
  const client = current?.created_by;
  const isResolved = !!current && DONE_STATUSES.includes(current.status.key);

  return (
    <div className="flex h-full min-h-0 flex-col bg-background">
      {/* Header */}
      <div className="flex items-center gap-3 border-b border-border bg-card px-5 py-3">
        {current ? (
          <>
            <PersonAvatar name={client?.name} email={client?.email} initials={client?.initials} avatarUrl={client?.avatar_url} size={40} />
            <div className="min-w-0 flex-1">
              <div className="flex items-center gap-2">
                <h3 className="truncate text-sm font-semibold text-foreground">{client ? displayName(client) : current.title}</h3>
                <span className={cn("shrink-0 rounded-full px-2 py-0.5 text-[10px] font-medium", TICKET_STATUS_COLORS[current.status.key])}>
                  {t.support.statuses[current.status.key] ?? (locale === "en" ? current.status.label_en : current.status.label)}
                </span>
              </div>
              <p className="truncate text-xs text-muted-foreground">
                {current.company?.name} · {current.title}
              </p>
            </div>
          </>
        ) : (
          <div className="flex-1" />
        )}

        {current && canManage && (
          <div className="flex shrink-0 items-center gap-2">
            <div className="hidden w-48 2xl:block">
              <AssigneeSelect
                value={current.assignee}
                disabled={updating}
                onChange={(userId) => runUpdate(() => container.ticketRepository.assign(conversationId, userId), t.support.toasts.assigned)}
                className="h-9 rounded-full"
              />
            </div>
            {!current.assignee && user && (
              <Button
                variant="outline"
                size="sm"
                className="rounded-full"
                title={t.support.assignToMe}
                disabled={updating}
                onClick={() => runUpdate(() => container.ticketRepository.assign(conversationId, user.id), t.support.toasts.assigned)}
              >
                <UserPlus className="h-3.5 w-3.5 2xl:mr-1.5" />
                <span className="hidden 2xl:inline">{t.support.assignToMe}</span>
              </Button>
            )}
            {isResolved ? (
              <Button
                variant="outline"
                size="sm"
                className="rounded-full"
                title={t.support.reopen}
                disabled={updating}
                onClick={() => runUpdate(() => container.ticketRepository.update(conversationId, { status: "Open" }), t.support.toasts.reopened)}
              >
                <RotateCcw className="h-3.5 w-3.5 xl:mr-1.5" />
                <span className="hidden xl:inline">{t.support.reopen}</span>
              </Button>
            ) : (
              <Button
                size="sm"
                className="rounded-full bg-emerald-600 text-white hover:bg-emerald-700"
                title={t.support.resolve}
                disabled={updating}
                onClick={() => runUpdate(() => container.ticketRepository.update(conversationId, { status: "Resolved" }), t.support.toasts.resolved)}
              >
                {updating ? <Loader2 className="h-3.5 w-3.5 animate-spin xl:mr-1.5" /> : <CheckCircle2 className="h-3.5 w-3.5 xl:mr-1.5" />}
                <span className="hidden xl:inline">{t.support.resolve}</span>
              </Button>
            )}
          </div>
        )}

        <HeaderIcon label={t.support.panel.details} onClick={onTogglePanel} className="hidden xl:inline-flex">
          {panelOpen ? <PanelRightClose className="h-4 w-4" /> : <PanelRightOpen className="h-4 w-4" />}
        </HeaderIcon>
        <HeaderIcon label={t.common.cancel} onClick={onClose} className="lg:hidden">
          <X className="h-4 w-4" />
        </HeaderIcon>
      </div>

      {/* Messages */}
      <div ref={scroller} className="min-h-0 flex-1 overflow-y-auto px-5 pb-4 [overflow-anchor:none]">
        {loading ? (
          <div className="flex h-full items-center justify-center">
            <Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
          </div>
        ) : failed ? (
          <div className="flex h-full items-center justify-center text-sm text-muted-foreground">{t.support.loadError}</div>
        ) : (
          <div ref={content} className="mx-auto max-w-3xl">
            {current && (
              <p className="pt-6 text-center text-[11px] text-muted-foreground">
                {t.support.thread.startedOn(formatDate(parseDate(current.created_at), locale))}
              </p>
            )}
            {/* What the client answered in the messenger before writing. */}
            {current?.category && (
              <div className="mt-3 flex flex-wrap items-center justify-center gap-2 text-xs">
                <span className="inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1 shadow-xs">
                  <span className="text-muted-foreground">{t.support.thread.subject}</span>
                  <span className="font-medium text-foreground">{locale === "en" ? current.category.label_en : current.category.label}</span>
                </span>
                <span className="inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1 shadow-xs">
                  <span className="text-muted-foreground">{t.support.thread.urgency}</span>
                  <span className="h-2 w-2 rounded-full" style={{ backgroundColor: current.priority.color ?? "#A1A1AA" }} />
                  <span className="font-medium text-foreground">{locale === "en" ? current.priority.label_en : current.priority.label}</span>
                </span>
              </div>
            )}
            {messages.map((message, index) => {
              const previous = messages[index - 1];
              const next = messages[index + 1];
              const newDay = !previous || !isSameDay(previous.created_at, message.created_at);
              const unreadStart = message.id === firstUnreadId;
              return (
                <Fragment key={message.id}>
                  {newDay && (
                    <div className="sticky top-2 z-10 mt-5 flex justify-center">
                      <span className="rounded-full border border-border bg-card/90 px-3 py-0.5 text-[11px] font-medium text-muted-foreground shadow-xs backdrop-blur">
                        {formatDayLabel(message.created_at, t, locale)}
                      </span>
                    </div>
                  )}
                  {unreadStart && (
                    <div ref={unreadMarker} className="my-4 flex items-center gap-3">
                      <span className="h-px flex-1 bg-red-300 dark:bg-red-900" />
                      <span className="text-[11px] font-semibold uppercase tracking-wide text-red-500">{t.support.thread.newMessages}</span>
                      <span className="h-px flex-1 bg-red-300 dark:bg-red-900" />
                    </div>
                  )}
                  <ThreadMessage
                    message={message}
                    fromTeam={fromTeam(message)}
                    isMine={message.author?.id === user?.id}
                    isFirstInGroup={newDay || unreadStart || !isSameGroup(previous, message)}
                    isLastInGroup={!next || next.id === firstUnreadId || !isSameGroup(message, next)}
                    onOpenImage={setLightbox}
                  />
                </Fragment>
              );
            })}
            {current?.status.key === "Resolved" && current.resolved_at && (
              <SystemLine tone="success">{t.support.thread.awaitingConfirmation(formatDateTime(parseDate(current.resolved_at), locale))}</SystemLine>
            )}
            {current?.status.key === "Closed" && current.closed_at && (
              <SystemLine tone="muted">{t.support.thread.closedOn(formatDateTime(parseDate(current.closed_at), locale))}</SystemLine>
            )}
          </div>
        )}
      </div>

      {current?.status.key === "Closed" && canManage && !loading && !failed && (
        <p className="border-t border-border bg-muted/40 px-5 py-2 text-center text-xs text-muted-foreground">{t.support.composer.repliesClosed}</p>
      )}
      {canManage && !loading && !failed && (
        <SupportComposer
          key={`${conversationId}-${current?.status.key === "Closed" ? "closed" : "open"}`}
          ticketId={conversationId}
          repliesClosed={current?.status.key === "Closed"}
          onSend={send}
        />
      )}

      {lightbox && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-6 backdrop-blur-sm" onClick={() => setLightbox(null)}>
          {/* eslint-disable-next-line @next/next/no-img-element */}
          <img src={lightbox.url} alt={lightbox.uploaded_file_name} onClick={(e) => e.stopPropagation()} className="max-h-[90vh] max-w-[90vw] rounded-xl object-contain shadow-2xl" />
          <button type="button" onClick={() => setLightbox(null)} className="absolute right-5 top-5 rounded-full bg-white/10 p-2 text-white hover:bg-white/20">
            <X className="h-5 w-5" />
          </button>
        </div>
      )}
    </div>
  );
}

function SystemLine({ tone, children }: { tone: "success" | "muted"; children: React.ReactNode }) {
  return (
    <div className="mt-5 flex justify-center">
      <span
        className={cn(
          "inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-[11px] font-medium",
          tone === "success"
            ? "bg-emerald-50 text-emerald-700 dark:bg-emerald-950/40 dark:text-emerald-400"
            : "bg-muted text-muted-foreground",
        )}
      >
        <CheckCircle2 className="h-3.5 w-3.5" />
        {children}
      </span>
    </div>
  );
}

function HeaderIcon({ label, onClick, className, children }: { label: string; onClick: () => void; className?: string; children: React.ReactNode }) {
  return (
    <Tooltip>
      <TooltipTrigger asChild>
        <button
          type="button"
          onClick={onClick}
          aria-label={label}
          className={cn("inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", className)}
        >
          {children}
        </button>
      </TooltipTrigger>
      <TooltipContent>{label}</TooltipContent>
    </Tooltip>
  );
}
