import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useExamSession, useExamAutoSave, useExamSubmit } from "@/hooks/queries/useExamSession";
import type { ExamAnswer, ExamOptionItem, ExamQuestion, ExamSessionSection } from "@/types";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Progress } from "@/components/ui/progress";
import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Flag, ChevronLeft, ChevronRight, Pause, Play, Mic, Square } from "lucide-react";
import { cn } from "@/lib/utils";
import { mediaService } from "@/services/media.service";

function normalizeType(t: string | null | undefined) {
  return (t ?? "").toLowerCase().replace(/\s+/g, "_");
}

function normalizeOption(o: ExamOptionItem): { label: string; text: string } {
  if (typeof o === "string") return { label: o, text: o };
  return { label: o.label ?? o.text ?? "", text: o.text ?? o.label ?? "" };
}

function countWords(s: string) {
  return s.trim() ? s.trim().split(/\s+/).length : 0;
}

function buildAnswerMap(sections: ExamSessionSection[]) {
  const map = new Map<number, ExamAnswer>();
  for (const sec of sections) {
    for (const q of sec.questions) {
      // The backend `/api/exam/<id>/session` endpoint returns a
      // `saved_answer` per question whenever a previous attempt is
      // resumed (browser refresh, network drop, accidental tab-close).
      // Seed the local map from that value so the student picks up
      // exactly where they left off — without this, autosaved answers
      // were silently dropped on every reload.
      const saved = (q as unknown as { saved_answer?: unknown }).saved_answer;
      const flagged = Boolean(
        (q as unknown as { flagged?: boolean }).flagged,
      );
      map.set(q.id, {
        question_id: q.id,
        answer:
          saved === undefined || saved === null
            ? null
            : (saved as ExamAnswer["answer"]),
        flagged,
        time_spent_ms: 0,
      });
    }
  }
  return map;
}

export default function ExamSession() {
  const { examId: examIdParam } = useParams();
  const examId = Number(examIdParam);
  const navigate = useNavigate();
  const { data: rawSession, isLoading, isError } = useExamSession(examId);
  const autoSave = useExamAutoSave();
  const submitMut = useExamSubmit();

  const session = useMemo(() => {
    if (!rawSession) return rawSession;
    const raw = rawSession as any;
    return {
      ...raw,
      title: raw.title || raw.exam_title || "",
      sections: (raw.sections || []).map((s: any) => ({
        ...s,
        questions: (s.questions || []).map((q: any) => ({
          ...q,
          type: q.type || q.question_type || q.skill || "",
        })),
      })),
    } as typeof rawSession;
  }, [rawSession]);

  const [sectionIdx, setSectionIdx] = useState(0);
  const [questionIdx, setQuestionIdx] = useState(0);
  const [answers, setAnswers] = useState<Map<number, ExamAnswer>>(new Map());
  const [sectionGate, setSectionGate] = useState<{ open: boolean; sec: number; remaining: number } | null>(null);
  const [reviewOpen, setReviewOpen] = useState(false);
  const [playing, setPlaying] = useState(false);

  const totalSecRef = useRef(0);
  const qStartRef = useRef(Date.now());

  useEffect(() => {
    if (!session) return;
    setAnswers(buildAnswerMap(session.sections));
    totalSecRef.current = session.total_time_min * 60;
    setTick(0);
  }, [session]);

  const section = session?.sections[sectionIdx];
  const question = section?.questions[questionIdx];
  const totalQuestions = useMemo(
    () => session?.sections.reduce((n, s) => n + s.questions.length, 0) ?? 0,
    [session],
  );

  const [tick, setTick] = useState(0);
  useEffect(() => {
    const id = window.setInterval(() => setTick((t) => t + 1), 1000);
    return () => window.clearInterval(id);
  }, []);

  useEffect(() => {
    qStartRef.current = Date.now();
  }, [question?.id]);

  const answeredCount = useMemo(() => {
    let n = 0;
    answers.forEach((a) => {
      if (a.answer === null || a.answer === "") return;
      if (Array.isArray(a.answer) && a.answer.length === 0) return;
      n += 1;
    });
    return n;
  }, [answers]);

  const progressPct = totalQuestions ? (answeredCount / totalQuestions) * 100 : 0;

  const updateAnswer = useCallback(
    (questionId: number, patch: Partial<ExamAnswer>) => {
      setAnswers((prev) => {
        const next = new Map(prev);
        const cur = next.get(questionId);
        if (!cur) return prev;
        const spent = Date.now() - qStartRef.current;
        next.set(questionId, { ...cur, ...patch, time_spent_ms: cur.time_spent_ms + spent });
        qStartRef.current = Date.now();
        return next;
      });
    },
    [],
  );

  const currentSectionAnswers = useCallback((): ExamAnswer[] => {
    if (!section) return [];
    return section.questions.map((q) => answers.get(q.id)!).filter(Boolean);
  }, [section, answers]);

  useEffect(() => {
    if (!session || !section) return;
    const attemptId = (session as any)?.attempt_id;
    const id = window.setInterval(() => {
      autoSave.mutate({
        examId,
        payload: { attempt_id: attemptId, section_id: section.id, answers: currentSectionAnswers() },
      });
    }, 10000);
    return () => window.clearInterval(id);
  }, [session, section, examId, autoSave, currentSectionAnswers]);

  const handleFlag = () => {
    if (!question) return;
    const cur = answers.get(question.id);
    if (!cur) return;
    updateAnswer(question.id, { flagged: !cur.flagged });
  };

  const goNext = () => {
    if (!session || !section) return;
    if (questionIdx < section.questions.length - 1) {
      setQuestionIdx((i) => i + 1);
      return;
    }
    if (sectionIdx < session.sections.length - 1) {
      setSectionGate({ open: true, sec: sectionIdx + 1, remaining: 5 });
      return;
    }
    setReviewOpen(true);
  };

  const goPrev = () => {
    if (questionIdx > 0) {
      setQuestionIdx((i) => i - 1);
      return;
    }
    if (sectionIdx > 0) {
      const prevSec = session!.sections[sectionIdx - 1];
      setSectionIdx((s) => s - 1);
      setQuestionIdx(prevSec.questions.length - 1);
    }
  };

  useEffect(() => {
    if (!sectionGate?.open) return;
    if (sectionGate.remaining <= 0) {
      setSectionIdx(sectionGate.sec);
      setQuestionIdx(0);
      setSectionGate(null);
      return;
    }
    const t = window.setTimeout(
      () => setSectionGate((g) => (g ? { ...g, remaining: g.remaining - 1 } : null)),
      1000,
    );
    return () => window.clearTimeout(t);
  }, [sectionGate]);

  const summary = useMemo(() => {
    let answered = 0;
    let unanswered = 0;
    let flagged = 0;
    session?.sections.forEach((sec) => {
      sec.questions.forEach((q) => {
        const a = answers.get(q.id);
        if (!a) {
          unanswered += 1;
          return;
        }
        if (a.flagged) flagged += 1;
        const empty =
          a.answer === null ||
          a.answer === "" ||
          (Array.isArray(a.answer) && a.answer.length === 0);
        if (empty) unanswered += 1;
        else answered += 1;
      });
    });
    return { answered, unanswered, flagged, total: totalQuestions };
  }, [session, answers, totalQuestions]);

  const renderQuestion = (q: ExamQuestion) => {
    const a = answers.get(q.id);
    if (!a) return null;
    const nt = normalizeType(q.type);
    const opts = (q.options ?? []).map(normalizeOption);

    if (nt.includes("listen") || q.audio_url) {
      return (
        <div className="space-y-4">
          <ListeningPlayer audioUrl={q.audio_url} audioBase64={q.audio_base64} />
          {opts.length ? (
            <RadioGroup
              value={typeof a.answer === "string" ? a.answer : ""}
              onValueChange={(v) => updateAnswer(q.id, { answer: v })}
              className="space-y-2"
            >
              {opts.map((o) => (
                <div key={o.label} className="flex items-center space-x-2">
                  <RadioGroupItem value={o.label} id={`${q.id}-${o.label}`} />
                  <Label htmlFor={`${q.id}-${o.label}`}>{o.text}</Label>
                </div>
              ))}
            </RadioGroup>
          ) : null}
        </div>
      );
    }

    if (nt.includes("multi") || nt === "mcq_multi") {
      const selected = Array.isArray(a.answer) ? a.answer : [];
      return (
        <div className="space-y-3">
          {opts.map((o) => (
            <div key={o.label} className="flex items-center space-x-2">
              <Checkbox
                id={`${q.id}-${o.label}`}
                checked={selected.includes(o.label)}
                onCheckedChange={(ck) => {
                  const on = ck === true;
                  const next = on ? [...selected, o.label] : selected.filter((x) => x !== o.label);
                  updateAnswer(q.id, { answer: next });
                }}
              />
              <Label htmlFor={`${q.id}-${o.label}`}>{o.text}</Label>
            </div>
          ))}
        </div>
      );
    }

    if (nt.includes("gap") || nt.includes("fill") || nt.includes("short_answer") || nt.includes("summary")) {
      return (
        <Input
          value={typeof a.answer === "string" ? a.answer : ""}
          onChange={(e) => updateAnswer(q.id, { answer: e.target.value })}
          placeholder="Your answer"
          className="max-w-md"
        />
      );
    }

    if (nt.includes("true") || nt.includes("tfng") || nt === "yes_no_not_given") {
      const tfOpts = opts.length
        ? opts
        : [
            { label: "TRUE", text: "TRUE" },
            { label: "FALSE", text: "FALSE" },
            { label: "NOT GIVEN", text: "NOT GIVEN" },
          ];
      return (
        <RadioGroup
          value={typeof a.answer === "string" ? a.answer : ""}
          onValueChange={(v) => updateAnswer(q.id, { answer: v })}
          className="space-y-2"
        >
          {tfOpts.map((o) => (
            <div key={o.label} className="flex items-center space-x-2">
              <RadioGroupItem value={o.label} id={`${q.id}-${o.label}`} />
              <Label htmlFor={`${q.id}-${o.label}`}>{o.text}</Label>
            </div>
          ))}
        </RadioGroup>
      );
    }

    if (nt.includes("writing") || nt.includes("essay")) {
      const text = typeof a.answer === "string" ? a.answer : "";
      const wc = countWords(text);
      const min = q.min_words ?? 150;
      return (
        <div className="space-y-2">
          <Textarea
            value={text}
            onChange={(e) => updateAnswer(q.id, { answer: e.target.value })}
            className="min-h-[200px]"
          />
          <p className={cn("text-sm", wc < min ? "text-amber-600" : "text-muted-foreground")}>
            {wc} words · minimum {min}
          </p>
        </div>
      );
    }

    if (nt.includes("speak") || nt.includes("record") || nt.includes("audio")) {
      return (
        <SpeakingRecorder
          questionId={q.id}
          onRecorded={(blob) => {
            const url = URL.createObjectURL(blob);
            updateAnswer(q.id, { answer: url });
          }}
        />
      );
    }

    return (
      <RadioGroup
        value={typeof a.answer === "string" ? a.answer : ""}
        onValueChange={(v) => updateAnswer(q.id, { answer: v })}
        className="space-y-2"
      >
        {opts.map((o) => (
          <div key={o.label} className="flex items-center space-x-2">
            <RadioGroupItem value={o.label} id={`${q.id}-${o.label}`} />
            <Label htmlFor={`${q.id}-${o.label}`}>{o.text}</Label>
          </div>
        ))}
      </RadioGroup>
    );
  };

  if (isLoading) {
    return (
      <div className="fixed inset-0 z-[100] flex items-center justify-center bg-background">
        <div className="h-10 w-10 animate-spin rounded-full border-2 border-primary border-t-transparent" />
      </div>
    );
  }

  if (isError || !session) {
    return (
      <div className="fixed inset-0 z-[100] flex flex-col items-center justify-center gap-4 bg-background p-6">
        <p className="text-destructive text-lg font-medium">Unable to load this exam session.</p>
        <p className="text-muted-foreground text-sm">The exam may not exist or the server is unreachable.</p>
        <Button variant="outline" onClick={() => navigate(-1)}>Go Back</Button>
      </div>
    );
  }

  const remainOverall = Math.max(0, totalSecRef.current - tick);
  const mm = Math.floor(remainOverall / 60);
  const ss = remainOverall % 60;

  return (
    <div className="fixed inset-0 z-[100] flex flex-col bg-background">
      <header className="flex flex-wrap items-center justify-between gap-4 border-b px-6 py-3">
        <div>
          <h1 className="text-lg font-semibold leading-tight">{session.title}</h1>
          <p className="text-sm text-muted-foreground">{section?.title}</p>
        </div>
        <div className="flex flex-wrap items-center gap-6">
          <span className="font-mono text-lg tabular-nums">
            {String(mm).padStart(2, "0")}:{String(ss).padStart(2, "0")}
          </span>
          <div className="w-48 space-y-1">
            <Progress value={progressPct} className="h-2" />
            <p className="text-xs text-muted-foreground">Overall progress</p>
          </div>
        </div>
      </header>

      <div className="flex flex-1 min-h-0 flex-col md:flex-row">
        {section?.passage_text ? (
          <ScrollArea className="w-full md:w-1/2 border-r p-6">
            <div className="prose prose-sm dark:prose-invert max-w-none">
              <h3 className="text-base font-semibold mb-2">{section.title}</h3>
              {section.difficulty && (
                <span className="inline-block text-xs font-medium bg-primary/10 text-primary px-2 py-0.5 rounded mb-3">
                  Level: {section.difficulty}
                </span>
              )}
              <div className="rounded-lg bg-muted/50 p-4 text-sm leading-relaxed whitespace-pre-line">
                {section.passage_text}
              </div>
            </div>
          </ScrollArea>
        ) : null}
        <ScrollArea className={cn("flex-1 p-6", section?.passage_text && "md:w-1/2")}>
          {question ? (
            <Card className="border-none shadow-none">
              <CardContent className="space-y-6 p-0">
                {section?.instructions_text ? (
                  <div className="rounded-lg bg-blue-50 dark:bg-blue-900/20 p-4 text-sm leading-relaxed border border-blue-200 dark:border-blue-800">
                    <p className="font-medium text-blue-800 dark:text-blue-200 mb-1">Instructions</p>
                    {section.instructions_text}
                  </div>
                ) : null}
                <div className="flex items-center gap-3">
                  <span className="inline-flex items-center justify-center h-7 w-7 rounded-full bg-primary text-primary-foreground text-xs font-bold">
                    {questionIdx + 1}
                  </span>
                  <span className="text-xs text-muted-foreground">
                    of {section?.questions.length ?? 0} · {question.marks} mark{question.marks !== 1 ? "s" : ""}
                  </span>
                </div>
                <div className="prose prose-sm dark:prose-invert max-w-none">
                  <p className="font-medium">{question.stem}</p>
                </div>
                {renderQuestion(question)}
              </CardContent>
            </Card>
          ) : null}
        </ScrollArea>
      </div>

      <footer className="flex flex-wrap items-center justify-between gap-3 border-t px-6 py-4">
        <div className="flex gap-2">
          <Button type="button" variant="outline" onClick={goPrev} disabled={sectionIdx === 0 && questionIdx === 0}>
            <ChevronLeft className="mr-1 h-4 w-4" />
            Previous
          </Button>
          <Button type="button" variant="outline" onClick={handleFlag}>
            <Flag className="mr-1 h-4 w-4" />
            Flag for Review
          </Button>
        </div>
        <div className="flex gap-2">
          <Button type="button" onClick={goNext}>
            Next
            <ChevronRight className="ml-1 h-4 w-4" />
          </Button>
        </div>
      </footer>

      <Dialog open={!!sectionGate?.open} onOpenChange={() => setSectionGate(null)}>
        <DialogContent className="sm:max-w-sm">
          <DialogHeader>
            <DialogTitle>Next section</DialogTitle>
            <DialogDescription>
              The next section begins in{" "}
              <span className="font-mono font-semibold">{sectionGate?.remaining ?? 0}</span>s.
            </DialogDescription>
          </DialogHeader>
        </DialogContent>
      </Dialog>

      <Dialog open={reviewOpen} onOpenChange={setReviewOpen}>
        <DialogContent className="max-w-lg">
          <DialogHeader>
            <DialogTitle>Review and submit</DialogTitle>
            <DialogDescription>
              {summary.answered} answered · {summary.unanswered} unanswered · {summary.flagged} flagged ·{" "}
              {summary.total} total
            </DialogDescription>
          </DialogHeader>
          <DialogFooter className="gap-2 sm:justify-end">
            <Button type="button" variant="outline" onClick={() => setReviewOpen(false)}>
              Back
            </Button>
            <Button
              type="button"
              onClick={() => {
                const attemptId = (session as any)?.attempt_id;
                const allAnswers = Array.from(answers.entries()).map(([qId, a]) => ({
                  question_id: qId,
                  answer: a.answer ?? "",
                }));
                submitMut.mutate(
                  { examId, attempt_id: attemptId, answers: allAnswers },
                  {
                    onSuccess: () => {
                      setReviewOpen(false);
                      navigate(`/student/exam/${examId}/status`);
                    },
                  },
                );
              }}
              disabled={submitMut.isPending}
            >
              Submit exam
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </div>
  );
}

function ListeningPlayer({ audioUrl, audioBase64 }: { audioUrl?: string; audioBase64?: string }) {
  const audioRef = useRef<HTMLAudioElement>(null);
  const [isPlaying, setIsPlaying] = useState(false);
  const [currentTime, setCurrentTime] = useState(0);
  const [duration, setDuration] = useState(0);

  const src = audioUrl || (audioBase64 ? `data:audio/mpeg;base64,${audioBase64}` : "");

  const formatTime = (sec: number) => {
    const m = Math.floor(sec / 60);
    const s = Math.floor(sec % 60);
    return `${m}:${s.toString().padStart(2, "0")}`;
  };

  return (
    <div className="space-y-2">
      {src ? (
        <>
          <audio
            ref={audioRef}
            src={src}
            onTimeUpdate={() => setCurrentTime(audioRef.current?.currentTime ?? 0)}
            onLoadedMetadata={() => setDuration(audioRef.current?.duration ?? 0)}
            onEnded={() => setIsPlaying(false)}
          />
          <div className="flex items-center gap-3 rounded-lg border bg-muted/40 p-4">
            <Button
              type="button" variant="outline" size="icon"
              onClick={() => {
                if (!audioRef.current) return;
                if (isPlaying) { audioRef.current.pause(); setIsPlaying(false); }
                else { audioRef.current.play(); setIsPlaying(true); }
              }}
            >
              {isPlaying ? <Pause className="h-5 w-5" /> : <Play className="h-5 w-5" />}
            </Button>
            <div className="h-2 flex-1 rounded-full bg-muted cursor-pointer" onClick={(e) => {
              if (!audioRef.current || !duration) return;
              const rect = e.currentTarget.getBoundingClientRect();
              const pct = (e.clientX - rect.left) / rect.width;
              audioRef.current.currentTime = pct * duration;
            }}>
              <div className="h-2 rounded-full bg-primary transition-all" style={{ width: duration ? `${(currentTime / duration) * 100}%` : "0%" }} />
            </div>
            <span className="text-sm text-muted-foreground tabular-nums">{formatTime(currentTime)} / {formatTime(duration)}</span>
          </div>
        </>
      ) : (
        <div className="flex items-center gap-3 rounded-lg border bg-muted/40 p-4 text-muted-foreground text-sm">
          Audio will be played by the examiner or is not yet available.
        </div>
      )}
    </div>
  );
}

function SpeakingRecorder({ questionId, onRecorded }: { questionId: number; onRecorded: (blob: Blob) => void }) {
  const [recording, setRecording] = useState(false);
  const [audioUrl, setAudioUrl] = useState<string | null>(null);
  const [elapsed, setElapsed] = useState(0);
  const mediaRecorderRef = useRef<MediaRecorder | null>(null);
  const chunksRef = useRef<Blob[]>([]);
  const timerRef = useRef<number>(0);

  const startRecording = async () => {
    try {
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      const mr = new MediaRecorder(stream, { mimeType: "audio/webm" });
      chunksRef.current = [];
      mr.ondataavailable = (e) => { if (e.data.size > 0) chunksRef.current.push(e.data); };
      mr.onstop = () => {
        const blob = new Blob(chunksRef.current, { type: "audio/webm" });
        const url = URL.createObjectURL(blob);
        setAudioUrl(url);
        onRecorded(blob);
        stream.getTracks().forEach((t) => t.stop());
      };
      mediaRecorderRef.current = mr;
      mr.start();
      setRecording(true);
      setElapsed(0);
      timerRef.current = window.setInterval(() => setElapsed((t) => t + 1), 1000);
    } catch {
      // microphone permission denied
    }
  };

  const stopRecording = () => {
    mediaRecorderRef.current?.stop();
    setRecording(false);
    window.clearInterval(timerRef.current);
  };

  const mm = Math.floor(elapsed / 60);
  const ss = elapsed % 60;

  return (
    <div className="rounded-lg border p-6 space-y-4">
      <div className="flex items-center justify-center gap-4">
        {!recording && !audioUrl && (
          <Button type="button" size="lg" onClick={startRecording} className="gap-2">
            <Mic className="h-5 w-5" /> Start Recording
          </Button>
        )}
        {recording && (
          <div className="flex items-center gap-4">
            <div className="h-3 w-3 rounded-full bg-red-500 animate-pulse" />
            <span className="font-mono text-lg tabular-nums">{String(mm).padStart(2, "0")}:{String(ss).padStart(2, "0")}</span>
            <Button type="button" variant="destructive" size="lg" onClick={stopRecording} className="gap-2">
              <Square className="h-4 w-4" /> Stop
            </Button>
          </div>
        )}
      </div>
      {audioUrl && (
        <div className="space-y-2">
          <audio controls src={audioUrl} className="w-full" />
          <div className="flex gap-2 justify-center">
            <Button type="button" variant="outline" size="sm" onClick={() => { setAudioUrl(null); setElapsed(0); }}>
              Re-record
            </Button>
          </div>
        </div>
      )}
    </div>
  );
}
