import { useEffect, useRef, useState } from "react";
import { attachFile, requestUploadUrl, sendMessageWithClientId, uploadToPresignedUrl } from "../api/chats";
import { useAuthStore } from "../store/authStore";
import { useChatStore } from "../store/chatStore";
import { buildWsUrl } from "../utils/ws";

export function MessageComposer() {
  const activeChatId = useChatStore((s) => s.activeChatId);
  const me = useAuthStore((s) => s.me);
  const draftsByChat = useChatStore((s) => s.draftsByChat);
  const setDraft = useChatStore((s) => s.setDraft);
  const clearDraft = useChatStore((s) => s.clearDraft);
  const addOptimisticMessage = useChatStore((s) => s.addOptimisticMessage);
  const confirmMessageByClientId = useChatStore((s) => s.confirmMessageByClientId);
  const removeOptimisticMessage = useChatStore((s) => s.removeOptimisticMessage);
  const replyToByChat = useChatStore((s) => s.replyToByChat);
  const setReplyToMessage = useChatStore((s) => s.setReplyToMessage);
  const accessToken = useAuthStore((s) => s.accessToken);
  const [text, setText] = useState("");
  const wsRef = useRef<WebSocket | null>(null);
  const recorderRef = useRef<MediaRecorder | null>(null);
  const chunksRef = useRef<BlobPart[]>([]);
  const [isUploading, setIsUploading] = useState(false);
  const [uploadProgress, setUploadProgress] = useState(0);
  const [uploadError, setUploadError] = useState<string | null>(null);
  const [selectedFile, setSelectedFile] = useState<File | null>(null);
  const [selectedType, setSelectedType] = useState<"file" | "image" | "video" | "audio">("file");
  const [previewUrl, setPreviewUrl] = useState<string | null>(null);
  const [isRecording, setIsRecording] = useState(false);

  useEffect(() => {
    if (!activeChatId) {
      if (text !== "") {
        setText("");
      }
      return;
    }
    const draft = draftsByChat[activeChatId] ?? "";
    if (draft !== text) {
      setText(draft);
    }
  }, [activeChatId, draftsByChat, text]);

  useEffect(() => {
    return () => {
      if (previewUrl) {
        URL.revokeObjectURL(previewUrl);
      }
    };
  }, [previewUrl]);

  function makeClientMessageId(): string {
    if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
      return crypto.randomUUID();
    }
    return `msg-${Date.now()}-${Math.random().toString(16).slice(2)}`;
  }

  function getWs(): WebSocket | null {
    if (!accessToken || !activeChatId) {
      return null;
    }
    if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
      return wsRef.current;
    }
    const wsUrl = buildWsUrl(accessToken);
    wsRef.current = new WebSocket(wsUrl);
    return wsRef.current;
  }

  async function handleSend() {
    if (!activeChatId || !text.trim() || !me) {
      return;
    }
    const clientMessageId = makeClientMessageId();
    const textValue = text.trim();
    const replyToMessageId = activeChatId ? (replyToByChat[activeChatId]?.id ?? undefined) : undefined;
    addOptimisticMessage({ chatId: activeChatId, senderId: me.id, type: "text", text: textValue, clientMessageId });
    try {
      const message = await sendMessageWithClientId(activeChatId, textValue, "text", clientMessageId, replyToMessageId);
      confirmMessageByClientId(activeChatId, clientMessageId, message);
      setText("");
      clearDraft(activeChatId);
      setReplyToMessage(activeChatId, null);
      const ws = getWs();
      ws?.send(JSON.stringify({ event: "typing_stop", payload: { chat_id: activeChatId } }));
    } catch {
      removeOptimisticMessage(activeChatId, clientMessageId);
      setUploadError("Message send failed. Please try again.");
    }
  }

  async function handleUpload(file: File, messageType: "file" | "image" | "video" | "audio" | "voice" = "file") {
    if (!activeChatId || !me) {
      return;
    }
    setIsUploading(true);
    setUploadProgress(0);
    setUploadError(null);
    const clientMessageId = makeClientMessageId();
    const replyToMessageId = activeChatId ? (replyToByChat[activeChatId]?.id ?? undefined) : undefined;
    try {
      const upload = await requestUploadUrl(file);
      await uploadToPresignedUrl(upload.upload_url, upload.required_headers, file, setUploadProgress);
      addOptimisticMessage({
        chatId: activeChatId,
        senderId: me.id,
        type: messageType,
        text: upload.file_url,
        clientMessageId
      });
      const message = await sendMessageWithClientId(activeChatId, upload.file_url, messageType, clientMessageId, replyToMessageId);
      await attachFile(message.id, upload.file_url, file.type || "application/octet-stream", file.size);
      confirmMessageByClientId(activeChatId, clientMessageId, message);
      setReplyToMessage(activeChatId, null);
    } catch {
      removeOptimisticMessage(activeChatId, clientMessageId);
      setUploadError("Upload failed. Please try again.");
    } finally {
      setIsUploading(false);
    }
  }

  function inferType(file: File): "file" | "image" | "video" | "audio" {
    if (file.type.startsWith("image/")) {
      return "image";
    }
    if (file.type.startsWith("video/")) {
      return "video";
    }
    if (file.type.startsWith("audio/")) {
      return "audio";
    }
    return "file";
  }

  function loadImageFromFile(file: File): Promise<HTMLImageElement> {
    return new Promise((resolve, reject) => {
      const image = new Image();
      const url = URL.createObjectURL(file);
      image.onload = () => {
        URL.revokeObjectURL(url);
        resolve(image);
      };
      image.onerror = () => {
        URL.revokeObjectURL(url);
        reject(new Error("Failed to load image"));
      };
      image.src = url;
    });
  }

  async function compressImageForWeb(file: File): Promise<File> {
    const image = await loadImageFromFile(file);
    const maxSide = 1920;
    const ratio = Math.min(1, maxSide / Math.max(image.width, image.height));
    const width = Math.max(1, Math.round(image.width * ratio));
    const height = Math.max(1, Math.round(image.height * ratio));

    const canvas = document.createElement("canvas");
    canvas.width = width;
    canvas.height = height;
    const ctx = canvas.getContext("2d");
    if (!ctx) {
      return file;
    }
    ctx.drawImage(image, 0, 0, width, height);

    const blob = await new Promise<Blob | null>((resolve) => {
      canvas.toBlob((result) => resolve(result), "image/jpeg", 0.82);
    });
    if (!blob || blob.size >= file.size) {
      return file;
    }
    const baseName = file.name.replace(/\.[^/.]+$/, "");
    return new File([blob], `${baseName || "image"}-web.jpg`, { type: "image/jpeg" });
  }

  async function prepareFileForUpload(file: File, fileType: "file" | "image" | "video" | "audio"): Promise<File> {
    if (fileType === "image") {
      return compressImageForWeb(file);
    }
    return file;
  }

  async function startRecord() {
    try {
      if (navigator.permissions && navigator.permissions.query) {
        const permission = await navigator.permissions.query({ name: "microphone" as PermissionName });
        if (permission.state === "denied") {
          setUploadError("Microphone access denied. Allow microphone in browser site permissions.");
          return;
        }
      }
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      const recorder = new MediaRecorder(stream);
      chunksRef.current = [];
      recorder.ondataavailable = (e) => chunksRef.current.push(e.data);
      recorder.onstop = async () => {
        const blob = new Blob(chunksRef.current, { type: "audio/webm" });
        const file = new File([blob], `voice-${Date.now()}.webm`, { type: "audio/webm" });
        setIsRecording(false);
        await handleUpload(file, "voice");
      };
      recorderRef.current = recorder;
      recorder.start();
      setIsRecording(true);
    } catch {
      setUploadError("Microphone access denied. Please allow microphone and retry.");
    }
  }

  function stopRecord() {
    recorderRef.current?.stop();
    recorderRef.current = null;
    setIsRecording(false);
  }

  function selectFile(file: File) {
    setUploadError(null);
    setSelectedFile(file);
    const fileType = inferType(file);
    setSelectedType(fileType);
    if (previewUrl) {
      URL.revokeObjectURL(previewUrl);
    }
    if (fileType === "image" || fileType === "video") {
      setPreviewUrl(URL.createObjectURL(file));
    } else {
      setPreviewUrl(null);
    }
  }

  async function sendSelectedFile() {
    if (!selectedFile) {
      return;
    }
    const uploadFile = await prepareFileForUpload(selectedFile, selectedType);
    await handleUpload(uploadFile, selectedType);
    if (previewUrl) {
      URL.revokeObjectURL(previewUrl);
    }
    setSelectedFile(null);
    setPreviewUrl(null);
    setSelectedType("file");
    setUploadProgress(0);
  }

  function cancelSelectedFile() {
    if (previewUrl) {
      URL.revokeObjectURL(previewUrl);
    }
    setSelectedFile(null);
    setPreviewUrl(null);
    setSelectedType("file");
    setUploadProgress(0);
    setUploadError(null);
  }

  function formatBytes(size: number): string {
    if (size < 1024) {
      return `${size} B`;
    }
    if (size < 1024 * 1024) {
      return `${(size / 1024).toFixed(1)} KB`;
    }
    return `${(size / (1024 * 1024)).toFixed(1)} MB`;
  }

  return (
    <div className="border-t border-slate-700/50 bg-slate-900/55 p-3">
      {activeChatId && replyToByChat[activeChatId] ? (
        <div className="mb-2 flex items-start justify-between rounded-lg border border-slate-700/80 bg-slate-800/70 px-3 py-2 text-xs">
          <div className="min-w-0">
            <p className="font-semibold text-sky-300">Replying</p>
            <p className="truncate text-slate-300">{replyToByChat[activeChatId]?.text || "[media]"}</p>
          </div>
          <button className="ml-2 rounded bg-slate-700 px-2 py-1 text-[11px]" onClick={() => setReplyToMessage(activeChatId, null)}>
            Cancel
          </button>
        </div>
      ) : null}
      <div className="mb-2 flex items-center gap-2">
        <label className="cursor-pointer rounded-full bg-slate-700/80 px-3 py-2 text-xs font-semibold hover:bg-slate-700">
          +
          <input
            className="hidden"
            type="file"
            disabled={isUploading}
            onChange={(e) => {
              const file = e.target.files?.[0];
              if (file) {
                selectFile(file);
              }
              e.currentTarget.value = "";
            }}
          />
        </label>
        <input
          className="flex-1 rounded-full border border-slate-700/80 bg-slate-800/80 px-4 py-2.5 text-sm outline-none placeholder:text-slate-400 focus:border-sky-500"
          placeholder="Write a message..."
          value={text}
          onChange={(e) => {
            const next = e.target.value;
            setText(next);
            if (activeChatId) {
              setDraft(activeChatId, next);
            }
            if (activeChatId) {
              const ws = getWs();
              ws?.send(JSON.stringify({ event: "typing_start", payload: { chat_id: activeChatId } }));
            }
          }}
        />
        <button className="rounded-full bg-sky-500 px-4 py-2.5 text-sm font-semibold text-slate-950 hover:bg-sky-400" onClick={handleSend}>
          Send
        </button>
      </div>
      {selectedFile ? (
        <div className="mb-2 rounded-xl border border-slate-700/80 bg-slate-900/95 p-3 text-sm">
          <div className="mb-2 font-semibold">Ready to send</div>
          <div className="mb-1 break-all text-slate-300">{selectedFile.name}</div>
          <div className="mb-2 text-xs text-slate-400">{formatBytes(selectedFile.size)}</div>
          {previewUrl && selectedType === "image" ? (
            <img className="mb-2 max-h-56 rounded object-contain" src={previewUrl} alt={selectedFile.name} />
          ) : null}
          {previewUrl && selectedType === "video" ? (
            <video className="mb-2 max-h-56 w-full rounded" src={previewUrl} controls muted />
          ) : null}
          {isUploading ? (
            <div className="mb-2">
              <div className="mb-1 text-xs text-slate-300">Uploading: {uploadProgress}%</div>
              <div className="h-2 rounded bg-slate-700">
                <div className="h-2 rounded bg-accent transition-all" style={{ width: `${uploadProgress}%` }} />
              </div>
            </div>
          ) : null}
          <div className="flex gap-2">
            <button
              className="rounded-lg bg-sky-500 px-3 py-1 font-semibold text-slate-950 disabled:opacity-50"
              onClick={() => void sendSelectedFile()}
              disabled={isUploading}
            >
              Send media
            </button>
            <button className="rounded-lg bg-slate-700 px-3 py-1 disabled:opacity-50" onClick={cancelSelectedFile} disabled={isUploading}>
              Cancel
            </button>
          </div>
        </div>
      ) : null}
      {uploadError ? <div className="mb-2 text-sm text-red-400">{uploadError}</div> : null}
      <div className="flex items-center gap-2 text-sm">
        <button className="rounded-lg bg-slate-700/90 px-3 py-1.5 disabled:opacity-50" onClick={startRecord} disabled={isUploading || isRecording}>
          {isRecording ? "Recording..." : "Record Voice"}
        </button>
        <button className="rounded-lg bg-slate-700/90 px-3 py-1.5 disabled:opacity-50" onClick={stopRecord} disabled={!isRecording}>
          Stop
        </button>
      </div>
    </div>
  );
}
