"use client";

import { useState, useRef } from "react";
import { motion, AnimatePresence } from "framer-motion";
import {
  Activity,
  Download,
  Upload,
  Server,
  RefreshCw,
  Zap,
} from "lucide-react";
import { CyberButton } from "../ui/cyber-button";
import { cn } from "@/lib/utils";
import { Dictionary } from "@/lib/IDict";

type TestStatus =
  | "idle"
  | "pinging"
  | "downloading"
  | "uploading"
  | "done"
  | "error";

interface SpeedtestClientProps {
  dict: Dictionary["speedtest"];
}

/**
 * Виджет спидтеста: пинг + скорость скачивания/отдачи через Rust-шлюз proxy-ws
 * (эндпоинты /api/speedtest/ping|download|upload, см. next.config.ts rewrites).
 *
 * В dev-режиме (NODE_ENV !== "production") реальные запросы не делаются — вместо
 * этого крутится runMockTest() с рандомными числами и искусственными задержками,
 * чтобы можно было верстать/проверять анимацию без поднятого локального proxy-ws.
 * В проде runRealTest() честно измеряет: пинг — 5 HEAD-подобных запросов подряд,
 * скачивание — читает поток ответа чанками и на лету пересчитывает Mbps раз в 500мс
 * (с потолком в 10 секунд, чтобы не скачивать бесконечно), отдача — грузит 10MB
 * случайных байт с таймаутом 15 секунд через AbortController.
 */
export function SpeedtestClient({ dict }: SpeedtestClientProps) {
  const [status, setStatus] = useState<TestStatus>("idle");
  const [metrics, setMetrics] = useState({
    ping: 0,
    download: 0.0,
    upload: 0.0,
  });

  const abortControllerRef = useRef<AbortController | null>(null);

  const getProxyUrl = () => {
    return process.env.NODE_ENV === "production"
      ? "/api/proxy"
      : "http://localhost:3001";
  };

  const runMockTest = async () => {
    const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms));

    setStatus("pinging");
    setMetrics({ ping: 0, download: 0, upload: 0 });
    await sleep(800);
    setMetrics((m) => ({ ...m, ping: Math.floor(Math.random() * 15) + 12 }));

    setStatus("downloading");
    for (let i = 1; i <= 20; i++) {
      await sleep(150);
      setMetrics((m) => ({
        ...m,
        download: +(15.5 * i + Math.random() * 10).toFixed(2),
      }));
    }

    setStatus("uploading");
    for (let i = 1; i <= 20; i++) {
      await sleep(150);
      setMetrics((m) => ({
        ...m,
        upload: +(8.2 * i + Math.random() * 5).toFixed(2),
      }));
    }

    setStatus("done");
  };

  const runRealTest = async () => {
    const baseUrl = getProxyUrl();
    setStatus("pinging");
    setMetrics({ ping: 0, download: 0, upload: 0 });

    const controller = new AbortController();
    abortControllerRef.current = controller;
    const signal = controller.signal;

    try {
      let totalPing = 0;
      let pingSuccessCount = 0;
      for (let i = 0; i < 5; i++) {
        const start = performance.now();
        try {
          await fetch(`${baseUrl}/api/speedtest/ping?nc=${Date.now()}`, {
            cache: "no-store",
            signal,
          });
          totalPing += performance.now() - start;
          pingSuccessCount++;
        } catch (e: any) {
          if (e?.name === "AbortError") throw e;
        }
      }
      if (pingSuccessCount === 0) throw new Error("Ping failed");
      setMetrics((m) => ({
        ...m,
        ping: Math.round(totalPing / pingSuccessCount),
      }));

      setStatus("downloading");
      const dlStart = performance.now();
      const dlRes = await fetch(
        `${baseUrl}/api/speedtest/download?nc=${Date.now()}`,
        { cache: "no-store", signal },
      );
      const reader = dlRes.body?.getReader();
      let receivedBytes = 0;
      const MAX_DL_TIME_MS = 10000;

      if (reader) {
        while (true) {
          const { done, value } = await reader.read();
          if (value) receivedBytes += value.length;

          const timeElapsed = performance.now() - dlStart;
          if (timeElapsed > 500) {
            const currentMbps =
              (receivedBytes * 8) / (1024 * 1024) / (timeElapsed / 1000);
            setMetrics((m) => ({
              ...m,
              download: Number(currentMbps.toFixed(2)),
            }));
          }

          if (done || timeElapsed > MAX_DL_TIME_MS) {
            if (!done) await reader.cancel();
            break;
          }
        }
      }

      setStatus("uploading");
      const UPLOAD_SIZE = 10 * 1024 * 1024;
      const uploadPayload = new Uint8Array(UPLOAD_SIZE);
      const chunk = new Uint8Array(65536);
      crypto.getRandomValues(chunk);
      for (let i = 0; i < UPLOAD_SIZE; i += chunk.length) {
        uploadPayload.set(
          chunk.subarray(0, Math.min(chunk.length, UPLOAD_SIZE - i)),
          i,
        );
      }

      const ulStart = performance.now();
      const uploadTimeoutId = setTimeout(() => controller.abort(), 15000);
      await fetch(`${baseUrl}/api/speedtest/upload?nc=${Date.now()}`, {
        method: "POST",
        body: uploadPayload,
        signal,
        headers: { "Content-Type": "application/octet-stream" },
      });
      clearTimeout(uploadTimeoutId);

      const ulTime = (performance.now() - ulStart) / 1000;
      const ulMbps = (UPLOAD_SIZE * 8) / (1024 * 1024) / ulTime;
      setMetrics((m) => ({ ...m, upload: Number(ulMbps.toFixed(2)) }));

      setStatus("done");
    } catch (err: any) {
      if (err.name !== "AbortError") console.error("Speedtest error:", err);
      setStatus("error");
    } finally {
      abortControllerRef.current = null;
    }
  };

  const runTest = () => {
    if (process.env.NODE_ENV !== "production") {
      runMockTest();
    } else {
      runRealTest();
    }
  };

  const isRunning =
    status !== "idle" && status !== "done" && status !== "error";

  const getStatusLabel = () => {
    switch (status) {
      case "pinging":
        return dict.statusPinging;
      case "downloading":
        return dict.statusDownloading;
      case "uploading":
        return dict.statusUploading;
      default:
        return status.toUpperCase();
    }
  };

  return (
    <div className="w-full max-w-4xl mx-auto bg-card/40 backdrop-blur-xl border border-border/50 rounded-[2rem] p-8 shadow-2xl relative overflow-hidden font-mono">
      <AnimatePresence>
        {isRunning && (
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            className="absolute inset-0 bg-gradient-to-tr from-primary/5 via-transparent to-secondary/5 pointer-events-none -z-10"
          />
        )}
      </AnimatePresence>

      <div className="flex flex-col md:flex-row items-center justify-between gap-8 mb-8 border-b border-border/50 pb-8">
        <div>
          <h2 className="text-2xl font-black uppercase text-foreground flex items-center gap-3">
            <Server
              className={cn(
                "size-6",
                isRunning
                  ? "text-primary animate-pulse"
                  : "text-muted-foreground",
              )}
            />
            {dict.title}
          </h2>
          <p className="text-muted-foreground text-sm mt-2 flex items-center gap-2">
            {process.env.NODE_ENV !== "production" ? (
              <span className="text-yellow-500 flex items-center gap-1">
                <Zap className="size-3" /> {dict.mockMode}
              </span>
            ) : (
              dict.prodMode
            )}
          </p>
        </div>

        <CyberButton onClick={runTest} disabled={isRunning} variant="primary">
          {!isRunning ? (
            <span className="flex items-center gap-2">
              <Activity className="size-4" />{" "}
              {status === "error"
                ? dict.btnError
                : status === "done"
                  ? dict.btnDone
                  : dict.btnIdle}
            </span>
          ) : (
            <span className="flex items-center gap-2 text-primary-foreground">
              <RefreshCw className="size-4 animate-spin" /> {getStatusLabel()}
              ...
            </span>
          )}
        </CyberButton>
      </div>

      <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
        <MetricCard
          title={dict.latency}
          value={metrics.ping}
          unit={dict.unitMs}
          icon={<Activity className="size-4" />}
          isActive={status === "pinging"}
          isDone={
            status === "downloading" ||
            status === "uploading" ||
            status === "done"
          }
          color="text-secondary"
          glowColor="shadow-secondary/20"
        />

        <MetricCard
          title={dict.download}
          value={metrics.download}
          unit={dict.unitMbps}
          icon={<Download className="size-4" />}
          isActive={status === "downloading"}
          isDone={status === "uploading" || status === "done"}
          color="text-green-500"
          glowColor="shadow-green-500/20"
        />

        <MetricCard
          title={dict.upload}
          value={metrics.upload}
          unit={dict.unitMbps}
          icon={<Upload className="size-4" />}
          isActive={status === "uploading"}
          isDone={status === "done"}
          color="text-primary"
          glowColor="shadow-primary/20"
        />
      </div>
    </div>
  );
}

function MetricCard({
  title,
  value,
  unit,
  icon,
  isActive,
  isDone,
  color,
  glowColor,
}: any) {
  return (
    <motion.div
      animate={{
        scale: isActive ? 1.02 : 1,
        borderColor: isActive
          ? "rgba(var(--primary), 0.5)"
          : "rgba(255,255,255,0.1)",
      }}
      className={cn(
        "relative p-6 rounded-2xl border transition-all duration-300 overflow-hidden bg-muted/10",
        isActive &&
          `shadow-[0_0_30px_-5px_rgba(0,0,0,0.3)] ${glowColor} bg-muted/30`,
      )}
    >
      <AnimatePresence>
        {isActive && (
          <motion.div
            initial={{ top: "-10%" }}
            animate={{ top: "110%" }}
            transition={{ repeat: Infinity, duration: 1.5, ease: "linear" }}
            className={cn(
              "absolute left-0 right-0 h-[2px] blur-[1px] opacity-50 z-0",
              color.replace("text-", "bg-"),
            )}
          />
        )}
      </AnimatePresence>

      <div className="relative z-10 text-muted-foreground text-xs uppercase tracking-widest mb-2 flex items-center justify-between">
        <span className="flex items-center gap-2">
          <span
            className={cn(isActive || isDone ? color : "text-muted-foreground")}
          >
            {icon}
          </span>
          {title}
        </span>
        {isActive && (
          <span
            className="flex size-2 rounded-full bg-current animate-ping"
            style={{ color: "currentColor" }}
          />
        )}
      </div>

      <div
        className={cn(
          "relative z-10 text-4xl font-black transition-colors duration-300",
          isActive || isDone ? "text-foreground" : "text-muted-foreground/50",
        )}
      >
        {value === 0 && !isDone && !isActive ? "--" : value}{" "}
        <span className="text-sm font-normal text-muted-foreground">
          {unit}
        </span>
      </div>

      <div className="absolute bottom-0 left-0 right-0 h-1 bg-black/20">
        <motion.div
          className={cn("h-full", color.replace("text-", "bg-"))}
          initial={{ width: "0%" }}
          animate={{
            width: isDone ? "100%" : isActive ? ["0%", "80%", "95%"] : "0%",
          }}
          transition={{ duration: isActive ? 10 : 0.5, ease: "easeOut" }}
        />
      </div>
    </motion.div>
  );
}
