"use client";

import Link from "next/link";
import * as React from "react";
import {
  ArrowDown,
  ArrowLeft,
  ArrowUp,
  Bell,
  BellOff,
  History,
  Loader2,
  Lock,
  MessagesSquare,
  Pencil,
} from "lucide-react";
import { toast } from "sonner";

import {
  addDiscussionCommentClient,
  editDiscussionCommentClient,
  fetchCommentHistoryClient,
  fetchMoreCommentsClient,
  fetchMoreRepliesClient,
  fetchThreadHistoryClient,
  subscribeDiscussionClient,
  voteDiscussionClient,
} from "@/app/customer/[tenant]/discussion-board/_lib/discussion-board-api";
import type {
  DiscussionComment,
  DiscussionCommentBucket,
  DiscussionCommentRevision,
  DiscussionTagOption,
  DiscussionThreadDetail,
  DiscussionThreadRevision,
} from "@/app/customer/[tenant]/discussion-board/_lib/types";
import { DISCUSSION_STATUS_LABELS } from "@/app/customer/[tenant]/discussion-board/_lib/types";
import { ErrorBanner } from "@/components/shared/error-banner";
import { TABLE_TOOLBAR_BTN_OUTLINE } from "@/components/shared/table-ui";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { customerUrl } from "@/lib/tenant";

type CustomerDiscussionViewPageProps = {
  tenant: string;
  threadToken: string;
  initialThread: DiscussionThreadDetail;
  initialBodyHtml: string;
  initialTags: DiscussionTagOption[];
  initialCommentBuckets: DiscussionCommentBucket[];
  initialReplyCountByRoot: Record<string, number>;
  initialCommentsHasMoreRoots: boolean;
  initialCommentsNextRootOffset: number;
  initialThreadActorVote: number;
  initialIsThreadAuthor: boolean;
  initialIsSubscribed: boolean;
  initialErrorMessage?: string | null;
  permissionDenied?: boolean;
};

function statusLabel(status: string) {
  return DISCUSSION_STATUS_LABELS[status] ?? status;
}

function VoteButtons({
  actorVote,
  upvoteCount,
  downvoteCount,
  disabled,
  onVote,
}: {
  actorVote: number;
  upvoteCount: number;
  downvoteCount: number;
  disabled?: boolean;
  onVote: (vote: "up" | "down") => void;
}) {
  return (
    <div className="flex items-center gap-1">
      <Button
        type="button"
        size="sm"
        variant={actorVote === 1 ? "default" : "outline"}
        className="h-7 gap-1 px-2 text-xs"
        disabled={disabled}
        onClick={() => onVote("up")}
      >
        <ArrowUp className="size-3.5" />
        {upvoteCount}
      </Button>
      <Button
        type="button"
        size="sm"
        variant={actorVote === -1 ? "default" : "outline"}
        className="h-7 gap-1 px-2 text-xs"
        disabled={disabled}
        onClick={() => onVote("down")}
      >
        <ArrowDown className="size-3.5" />
        {downvoteCount}
      </Button>
    </div>
  );
}

function CommentItem({
  comment,
  tenant,
  threadToken,
  isLocked,
  onReply,
  onEdited,
}: {
  comment: DiscussionComment;
  tenant: string;
  threadToken: string;
  isLocked: boolean;
  onReply: (parentId: number) => void;
  onEdited: (updated: DiscussionComment) => void;
}) {
  const [isEditing, setIsEditing] = React.useState(false);
  const [editBody, setEditBody] = React.useState(comment.body);
  const [editReason, setEditReason] = React.useState("");
  const [isSaving, setIsSaving] = React.useState(false);
  const [historyOpen, setHistoryOpen] = React.useState(false);
  const [historyLoading, setHistoryLoading] = React.useState(false);
  const [revisions, setRevisions] = React.useState<DiscussionCommentRevision[]>([]);

  const saveEdit = async () => {
    if (!editBody.trim()) {
      toast.error("Comment cannot be empty");
      return;
    }
    setIsSaving(true);
    try {
      const updated = await editDiscussionCommentClient(tenant, {
        id: comment.comment_id,
        body: editBody.trim(),
        edit_reason: editReason.trim() || undefined,
      });
      onEdited(updated);
      setIsEditing(false);
      toast.success("Comment updated");
    } catch (error) {
      toast.error(error instanceof Error ? error.message : "Failed to edit comment");
    } finally {
      setIsSaving(false);
    }
  };

  const openHistory = async () => {
    setHistoryOpen(true);
    setHistoryLoading(true);
    try {
      const data = await fetchCommentHistoryClient(tenant, comment.comment_id);
      setRevisions(data.revisions);
    } catch (error) {
      toast.error(error instanceof Error ? error.message : "Failed to load comment history");
    } finally {
      setHistoryLoading(false);
    }
  };

  return (
    <div className="rounded-md border bg-background p-3">
      <div className="mb-2 flex flex-wrap items-center justify-between gap-2">
        <div className="text-sm">
          <span className="font-medium">{comment.authorDisplayName}</span>
          <span className="text-muted-foreground ml-2 text-xs">{comment.date_added}</span>
        </div>
        <div className="flex gap-1">
          {comment.isAuthor ? (
            <>
              <Button type="button" variant="ghost" size="sm" className="h-7 px-2 text-xs" onClick={() => setIsEditing((v) => !v)}>
                <Pencil className="size-3.5" />
                Edit
              </Button>
              <Button type="button" variant="ghost" size="sm" className="h-7 px-2 text-xs" onClick={() => void openHistory()}>
                <History className="size-3.5" />
                History
              </Button>
            </>
          ) : null}
          {!isLocked ? (
            <Button
              type="button"
              variant="ghost"
              size="sm"
              className="h-7 px-2 text-xs"
              onClick={() => onReply(comment.comment_id)}
            >
              Reply
            </Button>
          ) : null}
        </div>
      </div>

      {isEditing ? (
        <div className="space-y-2">
          <Textarea value={editBody} onChange={(event) => setEditBody(event.target.value)} rows={3} />
          <Input
            value={editReason}
            onChange={(event) => setEditReason(event.target.value)}
            placeholder="Edit reason (optional)"
          />
          <div className="flex gap-2">
            <Button type="button" size="sm" disabled={isSaving} onClick={() => void saveEdit()}>
              {isSaving ? <Loader2 className="size-3.5 animate-spin" /> : null}
              Save
            </Button>
            <Button type="button" size="sm" variant="outline" onClick={() => setIsEditing(false)}>
              Cancel
            </Button>
          </div>
        </div>
      ) : (
        <p className="whitespace-pre-wrap text-sm">{comment.body}</p>
      )}

      <Dialog open={historyOpen} onOpenChange={setHistoryOpen}>
        <DialogContent className="max-h-[80vh] max-w-2xl overflow-y-auto">
          <DialogHeader>
            <DialogTitle>Comment history</DialogTitle>
          </DialogHeader>
          {historyLoading ? (
            <p className="text-muted-foreground text-sm">Loading…</p>
          ) : revisions.length === 0 ? (
            <p className="text-muted-foreground text-sm">No revisions found.</p>
          ) : (
            <div className="space-y-3">
              {revisions.map((rev) => (
                <div key={rev.revision_id} className="rounded border p-3 text-sm">
                  <div className="text-muted-foreground mb-1 text-xs">
                    #{rev.revision_number} · {rev.editorDisplayName} · {rev.date_added}
                  </div>
                  {rev.edit_reason ? (
                    <p className="mb-2 text-xs italic">Reason: {rev.edit_reason}</p>
                  ) : null}
                  <p className="whitespace-pre-wrap">{rev.body}</p>
                </div>
              ))}
            </div>
          )}
        </DialogContent>
      </Dialog>
    </div>
  );
}

function CommentBucketBlock({
  bucket,
  tenant,
  threadToken,
  isLocked,
  replyCount,
  onCommentAdded,
}: {
  bucket: DiscussionCommentBucket;
  tenant: string;
  threadToken: string;
  isLocked: boolean;
  replyCount: number;
  onCommentAdded: (comment: DiscussionComment, parentId: number | null) => void;
}) {
  const root = bucket.root;
  const [replies, setReplies] = React.useState(bucket.replies);
  const [replyOffset, setReplyOffset] = React.useState(bucket.replies.length);
  const [hasMoreReplies, setHasMoreReplies] = React.useState(
    root ? replyCount > bucket.replies.length : false,
  );
  const [loadingReplies, setLoadingReplies] = React.useState(false);
  const [replyToId, setReplyToId] = React.useState<number | null>(null);
  const [replyBody, setReplyBody] = React.useState("");
  const [isSubmitting, setIsSubmitting] = React.useState(false);

  const loadMoreReplies = async () => {
    if (!rootComment) return;
    setLoadingReplies(true);
    try {
      const data = await fetchMoreRepliesClient(tenant, {
        id: threadToken,
        parent_comment_id: rootComment.comment_id,
        offset: replyOffset,
      });
      setReplies((prev) => [...prev, ...data.replies]);
      setReplyOffset(data.nextOffset);
      setHasMoreReplies(data.hasMore);
    } catch (error) {
      toast.error(error instanceof Error ? error.message : "Failed to load replies");
    } finally {
      setLoadingReplies(false);
    }
  };

  const submitReply = async (parentId: number) => {
    if (!replyBody.trim()) {
      toast.error("Reply cannot be empty");
      return;
    }
    setIsSubmitting(true);
    try {
      const comment = await addDiscussionCommentClient(tenant, {
        id: threadToken,
        body: replyBody.trim(),
        parent_comment_id: parentId,
      });
      onCommentAdded(comment, parentId);
      if (rootComment && parentId === rootComment.comment_id) {
        setReplies((prev) => [...prev, comment]);
        setReplyOffset((prev) => prev + 1);
      }
      setReplyBody("");
      setReplyToId(null);
      toast.success("Reply added");
    } catch (error) {
      toast.error(error instanceof Error ? error.message : "Failed to add reply");
    } finally {
      setIsSubmitting(false);
    }
  };

  const [rootComment, setRootComment] = React.useState(root);

  const updateCommentInBucket = (updated: DiscussionComment) => {
    if (rootComment && updated.comment_id === rootComment.comment_id) {
      setRootComment(updated);
    }
    setReplies((prev) =>
      prev.map((item) => (item.comment_id === updated.comment_id ? updated : item)),
    );
  };

  if (!rootComment) return null;

  return (
    <div className="space-y-2">
      <CommentItem
        comment={rootComment}
        tenant={tenant}
        threadToken={threadToken}
        isLocked={isLocked}
        onReply={setReplyToId}
        onEdited={updateCommentInBucket}
      />
      {replies.length > 0 ? (
        <div className="ml-4 space-y-2 border-l pl-3">
          {replies.map((reply) => (
            <CommentItem
              key={reply.comment_id}
              comment={reply}
              tenant={tenant}
              threadToken={threadToken}
              isLocked={isLocked}
              onReply={setReplyToId}
              onEdited={updateCommentInBucket}
            />
          ))}
        </div>
      ) : null}
      {hasMoreReplies ? (
        <Button
          type="button"
          variant="ghost"
          size="sm"
          className="ml-4 h-7 text-xs"
          disabled={loadingReplies}
          onClick={() => void loadMoreReplies()}
        >
          {loadingReplies ? <Loader2 className="size-3.5 animate-spin" /> : null}
          Load more replies
        </Button>
      ) : null}
      {replyToId !== null && !isLocked ? (
        <div className="ml-4 space-y-2 border-l pl-3">
          <Label className="text-xs">Write a reply</Label>
          <Textarea value={replyBody} onChange={(event) => setReplyBody(event.target.value)} rows={3} />
          <div className="flex gap-2">
            <Button type="button" size="sm" disabled={isSubmitting} onClick={() => void submitReply(replyToId)}>
              {isSubmitting ? <Loader2 className="size-3.5 animate-spin" /> : null}
              Post reply
            </Button>
            <Button type="button" size="sm" variant="outline" onClick={() => setReplyToId(null)}>
              Cancel
            </Button>
          </div>
        </div>
      ) : null}
    </div>
  );
}

export function CustomerDiscussionViewPage({
  tenant,
  threadToken,
  initialThread,
  initialBodyHtml,
  initialTags,
  initialCommentBuckets,
  initialReplyCountByRoot,
  initialCommentsHasMoreRoots,
  initialCommentsNextRootOffset,
  initialThreadActorVote,
  initialIsThreadAuthor,
  initialIsSubscribed,
  initialErrorMessage = null,
  permissionDenied = false,
}: CustomerDiscussionViewPageProps) {
  const [thread, setThread] = React.useState(initialThread);
  const [bodyHtml] = React.useState(initialBodyHtml);
  const [tags] = React.useState(initialTags);
  const [commentBuckets, setCommentBuckets] = React.useState(initialCommentBuckets);
  const [replyCountByRoot, setReplyCountByRoot] = React.useState(initialReplyCountByRoot);
  const [hasMoreRoots, setHasMoreRoots] = React.useState(initialCommentsHasMoreRoots);
  const [nextRootOffset, setNextRootOffset] = React.useState(initialCommentsNextRootOffset);
  const [threadActorVote, setThreadActorVote] = React.useState(initialThreadActorVote);
  const [isSubscribed, setIsSubscribed] = React.useState(initialIsSubscribed);
  const [isSubscribing, setIsSubscribing] = React.useState(false);
  const [loadingMoreRoots, setLoadingMoreRoots] = React.useState(false);
  const [newCommentBody, setNewCommentBody] = React.useState("");
  const [isAddingComment, setIsAddingComment] = React.useState(false);
  const [historyOpen, setHistoryOpen] = React.useState(false);
  const [historyLoading, setHistoryLoading] = React.useState(false);
  const [threadRevisions, setThreadRevisions] = React.useState<DiscussionThreadRevision[]>([]);
  const [pageError] = React.useState(initialErrorMessage);

  const boardHref = customerUrl(tenant, "/discussion-board");
  const editHref = customerUrl(tenant, `/discussion-board/update/${encodeURIComponent(threadToken)}`);
  const isLocked =
    thread.is_locked ||
    thread.status === "closed" ||
    thread.status === "archived" ||
    thread.status === "inactive";

  const handleThreadVote = async (vote: "up" | "down") => {
    try {
      const result = await voteDiscussionClient(tenant, {
        target_type: "thread",
        target_id: thread.thread_id,
        vote,
      });
      setThreadActorVote(result.threadActorVote);
      if (result.upvote_count !== undefined || result.downvote_count !== undefined) {
        setThread((prev) => ({
          ...prev,
          upvote_count: result.upvote_count ?? prev.upvote_count,
          downvote_count: result.downvote_count ?? prev.downvote_count,
        }));
      }
    } catch (error) {
      toast.error(error instanceof Error ? error.message : "Failed to record vote");
    }
  };

  const toggleSubscribe = async () => {
    setIsSubscribing(true);
    try {
      const next = await subscribeDiscussionClient(tenant, {
        id: threadToken,
        subscribe: !isSubscribed,
      });
      setIsSubscribed(next);
      toast.success(next ? "Subscribed to thread" : "Unsubscribed from thread");
    } catch (error) {
      toast.error(error instanceof Error ? error.message : "Failed to update subscription");
    } finally {
      setIsSubscribing(false);
    }
  };

  const loadMoreRoots = async () => {
    setLoadingMoreRoots(true);
    try {
      const data = await fetchMoreCommentsClient(tenant, {
        id: threadToken,
        offset: nextRootOffset,
      });
      setCommentBuckets((prev) => [...prev, ...data.commentBuckets]);
      setReplyCountByRoot((prev) => ({ ...prev, ...data.replyCountByRoot }));
      setNextRootOffset(data.nextOffset);
      setHasMoreRoots(data.hasMore);
    } catch (error) {
      toast.error(error instanceof Error ? error.message : "Failed to load comments");
    } finally {
      setLoadingMoreRoots(false);
    }
  };

  const addRootComment = async () => {
    if (!newCommentBody.trim()) {
      toast.error("Comment cannot be empty");
      return;
    }
    setIsAddingComment(true);
    try {
      const comment = await addDiscussionCommentClient(tenant, {
        id: threadToken,
        body: newCommentBody.trim(),
      });
      setCommentBuckets((prev) => [...prev, { root: comment, replies: [] }]);
      setThread((prev) => ({ ...prev, comment_count: prev.comment_count + 1 }));
      setNewCommentBody("");
      toast.success("Comment added");
    } catch (error) {
      toast.error(error instanceof Error ? error.message : "Failed to add comment");
    } finally {
      setIsAddingComment(false);
    }
  };

  const openThreadHistory = async () => {
    setHistoryOpen(true);
    setHistoryLoading(true);
    try {
      const data = await fetchThreadHistoryClient(tenant, threadToken);
      setThreadRevisions(data.revisions);
    } catch (error) {
      toast.error(error instanceof Error ? error.message : "Failed to load thread history");
    } finally {
      setHistoryLoading(false);
    }
  };

  if (permissionDenied) {
    return (
      <div className="flex flex-col gap-3 md:gap-4">
        <div className="flex items-center gap-2 border-b pb-3">
          <MessagesSquare className="size-5 text-violet-600" />
          <h1 className="font-semibold text-base tracking-tight">Discussion</h1>
        </div>
        <ErrorBanner message="You do not have permission to access this thread." />
      </div>
    );
  }

  return (
    <div className="flex flex-col gap-3 md:gap-4">
      <div className="flex flex-col gap-2 border-b pb-3 sm:flex-row sm:items-center sm:justify-between">
        <div className="flex items-center gap-2">
          <MessagesSquare className="size-5 text-violet-600" />
          <h1 className="font-semibold text-base tracking-tight">{thread.title}</h1>
        </div>
        <Button variant="outline" size="sm" className="gap-1.5" asChild>
          <Link href={boardHref}>
            <ArrowLeft className="size-4" />
            Back to board
          </Link>
        </Button>
      </div>

      <ErrorBanner message={pageError} />

      <section className="space-y-3 rounded-lg border p-4">
        <div className="flex flex-wrap items-center gap-2">
          <Badge variant="secondary">{statusLabel(thread.status)}</Badge>
          {thread.is_locked ? (
            <Badge variant="outline" className="gap-1">
              <Lock className="size-3" />
              Locked
            </Badge>
          ) : null}
          <span className="text-muted-foreground text-xs">
            {thread.authorDisplayName} · {thread.published_at || thread.date_added}
          </span>
        </div>

        {tags.length > 0 ? (
          <div className="flex flex-wrap gap-1.5">
            {tags.map((tag) => (
              <Badge key={tag.tag_id} variant="outline" className="text-xs">
                {tag.label}
              </Badge>
            ))}
          </div>
        ) : null}

        <div
          className="prose prose-sm dark:prose-invert max-w-none"
          dangerouslySetInnerHTML={{ __html: bodyHtml }}
        />

        <div className="flex flex-wrap items-center gap-2 border-t pt-3">
          <VoteButtons
            actorVote={threadActorVote}
            upvoteCount={thread.upvote_count}
            downvoteCount={thread.downvote_count}
            onVote={(vote) => void handleThreadVote(vote)}
          />
          <Button
            type="button"
            {...TABLE_TOOLBAR_BTN_OUTLINE}
            className="h-7 gap-1 px-2 text-xs"
            disabled={isSubscribing}
            onClick={() => void toggleSubscribe()}
          >
            {isSubscribed ? <BellOff className="size-3.5" /> : <Bell className="size-3.5" />}
            {isSubscribed ? "Unsubscribe" : "Subscribe"}
          </Button>
          {initialIsThreadAuthor ? (
            <Button type="button" {...TABLE_TOOLBAR_BTN_OUTLINE} className="h-7 gap-1 px-2 text-xs" asChild>
              <Link href={editHref}>
                <Pencil className="size-3.5" />
                Edit
              </Link>
            </Button>
          ) : null}
          <Button
            type="button"
            variant="ghost"
            size="sm"
            className="h-7 gap-1 px-2 text-xs"
            onClick={() => void openThreadHistory()}
          >
            <History className="size-3.5" />
            History
          </Button>
        </div>
      </section>

      <section className="space-y-4 rounded-lg border p-4">
        <h2 className="text-sm font-semibold">
          Comments ({thread.comment_count})
        </h2>

        {!isLocked ? (
          <div className="space-y-2">
            <Label className="text-xs">Add a comment</Label>
            <Textarea
              value={newCommentBody}
              onChange={(event) => setNewCommentBody(event.target.value)}
              rows={3}
              placeholder="Write your comment…"
            />
            <Button type="button" size="sm" disabled={isAddingComment} onClick={() => void addRootComment()}>
              {isAddingComment ? <Loader2 className="size-3.5 animate-spin" /> : null}
              Post comment
            </Button>
          </div>
        ) : (
          <p className="text-muted-foreground text-sm">This thread is closed for new comments.</p>
        )}

        <div className="space-y-4">
          {commentBuckets.map((bucket, index) => {
            const rootId = bucket.root?.comment_id;
            const replyCount = rootId ? replyCountByRoot[String(rootId)] ?? bucket.replies.length : 0;
            return (
              <CommentBucketBlock
                key={rootId ?? `bucket-${index}`}
                bucket={bucket}
                tenant={tenant}
                threadToken={threadToken}
                isLocked={isLocked}
                replyCount={replyCount}
                onCommentAdded={() => {
                  setThread((prev) => ({ ...prev, comment_count: prev.comment_count + 1 }));
                }}
              />
            );
          })}
        </div>

        {hasMoreRoots ? (
          <Button
            type="button"
            variant="outline"
            size="sm"
            disabled={loadingMoreRoots}
            onClick={() => void loadMoreRoots()}
          >
            {loadingMoreRoots ? <Loader2 className="size-3.5 animate-spin" /> : null}
            Load more comments
          </Button>
        ) : null}
      </section>

      <Dialog open={historyOpen} onOpenChange={setHistoryOpen}>
        <DialogContent className="max-h-[80vh] max-w-2xl overflow-y-auto">
          <DialogHeader>
            <DialogTitle>Thread history</DialogTitle>
          </DialogHeader>
          {historyLoading ? (
            <p className="text-muted-foreground text-sm">Loading…</p>
          ) : threadRevisions.length === 0 ? (
            <p className="text-muted-foreground text-sm">No revisions found.</p>
          ) : (
            <div className="space-y-3">
              {threadRevisions.map((rev) => (
                <div key={rev.revision_id} className="rounded border p-3 text-sm">
                  <div className="text-muted-foreground mb-1 text-xs">
                    #{rev.revision_number} · {rev.editorDisplayName} · {rev.date_added}
                  </div>
                  {rev.edit_reason ? (
                    <p className="mb-2 text-xs italic">Reason: {rev.edit_reason}</p>
                  ) : null}
                  {rev.title ? <p className="mb-1 font-medium">{rev.title}</p> : null}
                  <div
                    className="prose prose-sm dark:prose-invert max-w-none"
                    dangerouslySetInnerHTML={{ __html: rev.body }}
                  />
                </div>
              ))}
            </div>
          )}
        </DialogContent>
      </Dialog>
    </div>
  );
}
