import {
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState,
} from "react";
import Hls from "hls.js";
import { OutletContext } from "../AppMainLayout";
import useMediaServiceV2 from "../../hooks/useMediaServiceV2";

const audioRegistry = new Set();
function registerAudio(audio) {
  audioRegistry.add(audio);
}
function unregisterAudio(audio) {
  audioRegistry.delete(audio);
}

/**
 * We change this to only PAUSE others,
 * without resetting their time to 0.
 */
function pauseAllAudio() {
  audioRegistry.forEach((a) => {
    try {
      a.pause();
    } catch {}
  });
}

const _audioInflight = new Map();
const _audioCache = new Map();

function extractUrl(res) {
  if (!res) return null;
  if (typeof res === "string") return res;
  if (typeof res?.url === "string") return res.url;
  if (typeof res?.data?.url === "string") return res.data.url;
  if (typeof res?.data?.data?.url === "string") return res.data.data.url;
  if (typeof res?.result?.url === "string") return res.result.url;
  return null;
}

function normalizeUrl(u) {
  if (!u) return null;
  if (u === "undefined" || u === "null") return null;
  if (typeof u === "string" && u.trim() === "") return null;
  return u;
}

function parsePresignedExpiry(url) {
  try {
    const u = new URL(url);
    const expiresSec = Number(u.searchParams.get("X-Amz-Expires"));
    const dateStr = u.searchParams.get("X-Amz-Date");
    if (!expiresSec || !dateStr) return null;
    const yyyy = Number(dateStr.slice(0, 4));
    const mm = Number(dateStr.slice(4, 6));
    const dd = Number(dateStr.slice(6, 8));
    const HH = Number(dateStr.slice(9, 11));
    const MM = Number(dateStr.slice(11, 13));
    const SS = Number(dateStr.slice(13, 15));
    const start = Date.UTC(yyyy, mm - 1, dd, HH, MM, SS);
    return start + expiresSec * 1000;
  } catch {
    return null;
  }
}

function isFresh(entry) {
  if (!entry?.url) return false;
  if (entry.expiresAt == null) return true;
  return entry.expiresAt - Date.now() > 30_000;
}

function isPresignedUrl(u) {
  if (!u) return false;
  if (u.startsWith("blob:")) return true;
  return /X-Amz-Algorithm=|X-Amz-Signature=|X-Amz-Credential=|digitaloceanspaces\.com/i.test(
    u,
  );
}

function withAccountParam(u, accountId) {
  if (!u || !accountId) return u;
  if (/_account=/.test(u)) return u;
  const sep = u.includes("?") ? "&" : "?";
  return `${u}${sep}_account=${encodeURIComponent(accountId)}`;
}

async function resolveAudioUrlDeduped(cacheKey, fileId, resolver) {
  if (!fileId) return null;
  const cached = _audioCache.get(cacheKey);
  if (isFresh(cached)) return cached.url;
  if (_audioInflight.has(cacheKey)) return _audioInflight.get(cacheKey);

  const p = (async () => {
    const res = await resolver(fileId);
    const url = normalizeUrl(extractUrl(res));
    if (!url) return null;
    const expiresAt = parsePresignedExpiry(url);
    _audioCache.set(cacheKey, { url, expiresAt });
    return url;
  })().finally(() => {
    _audioInflight.delete(cacheKey);
  });

  _audioInflight.set(cacheKey, p);
  return p;
}

const useAudioPlayer = ({ src, streamSrc, fileId, debug = false }) => {
  const { accessToken, generalData } = useContext(OutletContext);

  const getAccountIdFromCookie = () => {
    const cookies = document.cookie.split(";");
    for (let i = 0; i < cookies.length; i++) {
      const cookie = cookies[i].trim();
      if (cookie.startsWith("defaultAccountID="))
        return cookie.substring("defaultAccountID=".length);
    }
    return null;
  };

  const accountId =
    generalData?.selectedAccount?.id || getAccountIdFromCookie();

  const mediaService = useMediaServiceV2();

  const log = useCallback(
    (...args) => {
      if (debug) console.log("[useAudioPlayer]", ...args);
    },
    [debug],
  );

  const audioRef = useRef(null);
  if (!audioRef.current) audioRef.current = new Audio();

  const hlsRef = useRef(null);
  const resolverRef = useRef(null);
  const playbackRateRef = useRef(1);

  useEffect(() => {
    resolverRef.current = mediaService?.resolvePresignedUrl;
  }, [mediaService]);

  const [isPlaying, setIsPlaying] = useState(false);
  const [isBuffering, setIsBuffering] = useState(false);
  const [isReady, setIsReady] = useState(false);
  const [error, setError] = useState(null);
  const [progress, setProgress] = useState({ currentTime: 0, duration: 0 });
  const [playbackRate, setPlaybackRate] = useState(1);
  const [resolvedUrl, setResolvedUrl] = useState(null);
  const [isResolvingUrl, setIsResolvingUrl] = useState(false);
  const [resolveFailed, setResolveFailed] = useState(false);

  const fileIdMode = !!fileId && !src;

  const updateSpeed = useCallback((speedCount) => {
    const audio = audioRef.current;
    if (!audio) return;
    const newRate = Number(speedCount) || 1;
    playbackRateRef.current = newRate;
    setPlaybackRate(newRate);
    audio.playbackRate = newRate;
  }, []);

  useEffect(() => {
    playbackRateRef.current = playbackRate;
    const audio = audioRef.current;
    if (audio && isReady) {
      audio.playbackRate = playbackRate;
    }
  }, [playbackRate, isReady]);

  useEffect(() => {
    let cancelled = false;

    if (src || !fileId) {
      setResolvedUrl(null);
      setResolveFailed(false);
      setError(null);
      return;
    }

    const resolver = resolverRef.current;
    if (typeof resolver !== "function") {
      if (mediaService && fileId) {
        setResolveFailed(true);
        setError({ type: "resolve", reason: "resolver_unavailable" });
      }
      return;
    }

    setIsResolvingUrl(true);
    setResolveFailed(false);
    setError(null);
    const cacheKey = `${accountId || "na"}:${fileId}`;

    resolveAudioUrlDeduped(cacheKey, fileId, resolver)
      .then((url) => {
        if (cancelled) return;
        if (!url) {
          setResolvedUrl(null);
          setResolveFailed(true);
          setError({ type: "resolve", reason: "empty_url" });
        } else {
          setResolvedUrl(url);
          setResolveFailed(false);
        }
      })
      .catch((err) => {
        if (cancelled) return;
        setResolvedUrl(null);
        setResolveFailed(true);
        setError({ type: "resolve", error: err });
      })
      .finally(() => {
        if (!cancelled) setIsResolvingUrl(false);
      });

    return () => {
      cancelled = true;
    };
  }, [fileId, src, accountId]);

  const playbackUrl = useMemo(() => {
    if (src) return normalizeUrl(src);
    return normalizeUrl(fileIdMode ? resolvedUrl : streamSrc || resolvedUrl);
  }, [src, fileIdMode, resolvedUrl, streamSrc]);

  const isHls = useMemo(
    () => (playbackUrl ? /\.m3u8(\?|$)/i.test(playbackUrl) : false),
    [playbackUrl],
  );

  useEffect(() => {
    const audio = audioRef.current;
    registerAudio(audio);
    return () => unregisterAudio(audio);
  }, []);

  const cleanupPipeline = useCallback(() => {
    if (hlsRef.current) {
      hlsRef.current.destroy();
      hlsRef.current = null;
    }
    const audio = audioRef.current;
    try {
      audio.pause();
      audio.removeAttribute("src");
      audio.load();
    } catch {}
  }, []);

  useEffect(() => {
    const audio = audioRef.current;
    if (!audio) return;

    if (!playbackUrl) {
      setIsReady(false);
      return;
    }

    setIsReady(false);
    setIsBuffering(false);
    setError(null);
    cleanupPipeline();

    const finalUrl = isPresignedUrl(playbackUrl)
      ? playbackUrl
      : withAccountParam(playbackUrl, accountId);

    audio.playbackRate = playbackRateRef.current;

    const onTimeUpdate = () =>
      setProgress({
        currentTime: audio.currentTime || 0,
        duration: audio.duration || 0,
      });

    const onLoadedMetadata = () => {
      setIsReady(true);
      setError(null);
      audio.playbackRate = playbackRateRef.current;

      // ✅ FIX: Set progress immediately when ready
      setProgress({
        currentTime: audio.currentTime || 0,
        duration: audio.duration || 0,
      });
    };

    const onError = () => {
      setIsReady(false);
      setIsBuffering(false);
      setIsPlaying(false);
      setError({
        type: "audio",
        code: audio.error?.code,
        message: audio.error?.message,
      });
    };

    const onWaiting = () => setIsBuffering(true);
    const onEnded = () => setIsPlaying(false);
    const onPause = () => setIsPlaying(false);
    const onPlaying = () => {
      setIsPlaying(true);
      setIsBuffering(false);
    };

    audio.addEventListener("ended", onEnded);
    audio.addEventListener("pause", onPause);
    audio.addEventListener("playing", onPlaying);
    audio.addEventListener("waiting", onWaiting);
    audio.addEventListener("error", onError);
    audio.addEventListener("timeupdate", onTimeUpdate);
    audio.addEventListener("loadedmetadata", onLoadedMetadata);

    if (isHls && Hls.isSupported()) {
      const hls = new Hls({
        lowLatencyMode: true,
        xhrSetup: (xhr, u) => {
          const finalU = isPresignedUrl(u) ? u : withAccountParam(u, accountId);
          xhr.open("GET", finalU, true);
          if (!isPresignedUrl(u) && accessToken)
            xhr.setRequestHeader("Authorization", `Bearer ${accessToken}`);
        },
      });

      hlsRef.current = hls;
      hls.attachMedia(audio);
      hls.on(Hls.Events.MEDIA_ATTACHED, () => hls.loadSource(finalUrl));
      hls.on(Hls.Events.MANIFEST_PARSED, () => {
        setIsReady(true);
        setError(null);

        // ✅ ALSO ensure progress for HLS
        setProgress({
          currentTime: audio.currentTime || 0,
          duration: audio.duration || 0,
        });
      });
      hls.on(Hls.Events.ERROR, (_evt, data) => {
        if (!data?.fatal) return;
        setIsReady(false);
        setIsBuffering(false);
        setIsPlaying(false);
        setError({ type: "hls", data });
      });
    } else {
      audio.src = finalUrl;
      audio.load();
    }

    return () => {
      audio.removeEventListener("ended", onEnded);
      audio.removeEventListener("pause", onPause);
      audio.removeEventListener("playing", onPlaying);
      audio.removeEventListener("waiting", onWaiting);
      audio.removeEventListener("error", onError);
      audio.removeEventListener("timeupdate", onTimeUpdate);
      audio.removeEventListener("loadedmetadata", onLoadedMetadata);
      cleanupPipeline();
    };
  }, [
    playbackUrl,
    isHls,
    accessToken,
    accountId,
    cleanupPipeline,
  ]);

  const play = useCallback(() => {
    const audio = audioRef.current;
    if (audio && isReady) {
      pauseAllAudio();
      audio.play().catch((e) => log("play() fail", e));
    }
  }, [isReady, log]);

  const pause = useCallback(() => {
    audioRef.current?.pause();
  }, []);

  return {
    stop: () => {
      if (audioRef.current) {
        audioRef.current.pause();
        audioRef.current.currentTime = 0;
      }
    },
    audio: audioRef.current,
    play,
    pause,
    isPlaying,
    isBuffering,
    isReady,
    isResolvingUrl,
    error,
    progress,
    speed: playbackRate,
    updateSpeed,
  };
};

export default useAudioPlayer;
