'use client';

import { useEffect, useMemo, useState } from 'react';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
import { Loader2 } from 'lucide-react';
import { lenientJsonParse } from '@/lib/ai/lenientJson';
import { estimateCost, formatCost } from '@/lib/ai/pricing';

const DAY_LABELS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];

interface AIExercise {
  exerciseId: string | null;
  exerciseName: string;
  order: number;
  sets?: number | null;
  repsMin?: number | null;
  repsMax?: number | null;
  rpe?: number | null;
  restSeconds?: number | null;
  suggestedWeight?: number | null;
  suggestedWeightUnit?: 'lbs' | 'kg' | null;
  notes?: string | null;
}
interface AIDay {
  dayOfWeek: number;
  name?: string | null;
  description?: string | null;
  exercises: AIExercise[];
}
interface AIWeek {
  weekNumber: number;
  phase?: string | null;
  description?: string | null;
  days: AIDay[];
}
interface AIProgram {
  name: string;
  description?: string | null;
  type: string;
  durationWeeks: number;
  weeks: AIWeek[];
}

interface LibraryExercise {
  id: string;
  name: string;
  type: string;
}

interface Row {
  id: string;
  templateName: string | null;
  userInput: string;
  systemPrompt: string;
  userPrompt: string;
  rawResponse: string | null;
  parsedProgram: string | null;
  progressText: string | null;
  provider: string;
  model: string;
  tokensIn: number | null;
  tokensOut: number | null;
  durationMs: number | null;
  status: string;
  errorMessage: string | null;
  appliedProgramId: string | null;
  createdAt: string;
}

/**
 * Client-side detail view for an AIGeneration. Three modes:
 *
 * - PENDING: poll for progress + render the live partial-JSON preview.
 *   The runner keeps writing `progressText` even if no SSE clients
 *   are subscribed, so polling works for cross-process resume too.
 *
 * - COMPLETED: render the parsed program tree with an Apply button.
 *   Same UI as the Generate page's preview, factored out below.
 *
 * - APPLIED: the user already turned this into a Program; show a
 *   link there. Re-applying isn't allowed (would create a duplicate).
 *
 * - FAILED: error message + raw response collapsed by default.
 */
export default function GenerationDetail({
  row: initialRow,
  exercises,
}: {
  row: Row;
  exercises: LibraryExercise[];
}) {
  const router = useRouter();
  const [row, setRow] = useState(initialRow);

  // Poll while pending. 1.5s cadence — fast enough to feel live,
  // gentle on the DB. Stops when status flips terminal.
  useEffect(() => {
    if (row.status !== 'pending') return;
    let cancelled = false;
    const tick = async () => {
      try {
        const r = await fetch(`/api/ai/generations/${row.id}`);
        if (!r.ok || cancelled) return;
        const fresh = await r.json();
        if (cancelled) return;
        setRow({
          ...fresh,
          createdAt:
            typeof fresh.createdAt === 'string'
              ? fresh.createdAt
              : new Date(fresh.createdAt).toISOString(),
        });
      } catch {
        /* transient — try again */
      }
    };
    const id = setInterval(tick, 1500);
    return () => {
      cancelled = true;
      clearInterval(id);
    };
  }, [row.id, row.status]);

  const cost = useMemo(
    () =>
      estimateCost({
        provider: row.provider,
        model: row.model,
        tokensIn: row.tokensIn,
        tokensOut: row.tokensOut,
      }),
    [row.provider, row.model, row.tokensIn, row.tokensOut],
  );

  // Live partial during pending.
  const partial = useMemo(
    () =>
      row.status === 'pending' && row.progressText
        ? (lenientJsonParse(row.progressText) as Partial<AIProgram> | null)
        : null,
    [row.status, row.progressText],
  );

  const parsedProgram = useMemo(
    () =>
      row.parsedProgram ? (JSON.parse(row.parsedProgram) as AIProgram) : null,
    [row.parsedProgram],
  );

  return (
    <div className="space-y-5">
      {/* Header / metadata */}
      <header className="space-y-2">
        <div className="flex items-center gap-2 text-xs text-zinc-500 uppercase tracking-wider flex-wrap">
          <StatusPill status={row.status} />
          <span>{new Date(row.createdAt).toLocaleString()}</span>
          <span className="text-zinc-600">·</span>
          <span>
            {row.provider} · {row.model}
          </span>
          {row.tokensIn != null && (
            <>
              <span className="text-zinc-600">·</span>
              <span>
                {row.tokensIn} in · {row.tokensOut ?? '?'} out
              </span>
            </>
          )}
          {cost != null && (
            <>
              <span className="text-zinc-600">·</span>
              <span>{formatCost(cost)}</span>
            </>
          )}
          {row.durationMs != null && (
            <>
              <span className="text-zinc-600">·</span>
              <span>{formatDuration(row.durationMs)}</span>
            </>
          )}
        </div>
        {row.templateName && (
          <p className="text-xs text-zinc-400">
            Template: <span className="text-zinc-200">{row.templateName}</span>
          </p>
        )}
      </header>

      {/* User's prompt */}
      <section className="bg-zinc-900 border border-zinc-800 rounded p-4">
        <h2 className="text-xs font-semibold text-zinc-400 uppercase tracking-wider mb-2">
          Your specifics
        </h2>
        <p className="text-sm text-zinc-200 whitespace-pre-wrap">{row.userInput}</p>
      </section>

      {/* Pending: live preview */}
      {row.status === 'pending' && (
        <section className="space-y-3">
          <div className="rounded bg-blue-950/30 border border-blue-900 px-4 py-3 text-xs text-blue-200">
            <p className="font-bold text-blue-100 mb-1 flex items-center gap-2">
              <Loader2 className="w-3 h-3 animate-spin" />
              Still generating…
            </p>
            <p>
              Polling every 1.5s for progress. Safe to leave this page —
              the model keeps running on the server and you&apos;ll see the
              result when you come back.
            </p>
          </div>
          {partial ? (
            <PartialTree partial={partial} />
          ) : (
            <div className="text-xs text-zinc-500 italic flex items-center gap-2">
              <Loader2 className="w-3 h-3 animate-spin" />
              Waiting for the first parseable JSON…
            </div>
          )}
        </section>
      )}

      {/* Failed */}
      {row.status === 'failed' && (
        <section className="space-y-3">
          <div className="bg-red-950/40 border border-red-900 rounded p-3 text-sm text-red-300">
            {row.errorMessage ?? 'Failed.'}
          </div>
          <Link
            href="/main/ai/generate"
            className="inline-block text-xs text-zinc-400 underline hover:text-white"
          >
            ← Try again from Generate
          </Link>
        </section>
      )}

      {/* Applied — link to the program */}
      {row.status === 'applied' && row.appliedProgramId && (
        <section>
          <Link
            href={`/main/programs/${row.appliedProgramId}`}
            className="inline-block px-4 py-2 rounded bg-emerald-700 text-white text-xs uppercase tracking-wider font-bold hover:bg-emerald-600"
          >
            View applied program →
          </Link>
        </section>
      )}

      {/* Completed (not yet applied) — show preview + Apply */}
      {row.status === 'completed' && parsedProgram && (
        <ProgramPreview
          generationId={row.id}
          program={parsedProgram}
          exercises={exercises}
          onApplied={(programId) => router.push(`/main/programs/${programId}`)}
        />
      )}

      {/* Raw response + prompts (collapsed) */}
      {row.rawResponse && (
        <details className="text-xs text-zinc-500">
          <summary className="cursor-pointer">Raw model response</summary>
          <pre className="bg-zinc-950 border border-zinc-800 rounded p-3 mt-2 whitespace-pre-wrap max-h-96 overflow-auto">
            {row.rawResponse}
          </pre>
        </details>
      )}
      <details className="text-xs text-zinc-500">
        <summary className="cursor-pointer">Exact prompts sent</summary>
        <div className="mt-2 space-y-2">
          <div>
            <p className="font-semibold text-zinc-400 uppercase tracking-wider mb-1">
              System
            </p>
            <pre className="bg-zinc-950 border border-zinc-800 rounded p-3 whitespace-pre-wrap max-h-72 overflow-auto">
              {row.systemPrompt}
            </pre>
          </div>
          <div>
            <p className="font-semibold text-zinc-400 uppercase tracking-wider mb-1">
              User
            </p>
            <pre className="bg-zinc-950 border border-zinc-800 rounded p-3 whitespace-pre-wrap max-h-72 overflow-auto">
              {row.userPrompt}
            </pre>
          </div>
        </div>
      </details>
    </div>
  );
}

function ProgramPreview({
  generationId,
  program: initial,
  exercises,
  onApplied,
}: {
  generationId: string;
  program: AIProgram;
  exercises: LibraryExercise[];
  onApplied: (programId: string) => void;
}) {
  const [program, setProgram] = useState<AIProgram>(initial);
  const [applying, setApplying] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [startDate, setStartDate] = useState(
    new Date().toISOString().slice(0, 10),
  );
  const [activate, setActivate] = useState(true);

  const exerciseLookup = useMemo(
    () => new Map(exercises.map((e) => [e.id, e])),
    [exercises],
  );
  const unresolvedCount = useMemo(() => {
    let n = 0;
    for (const w of program.weeks)
      for (const d of w.days)
        for (const ex of d.exercises) {
          if (!ex.exerciseId || !exerciseLookup.has(ex.exerciseId)) n++;
        }
    return n;
  }, [program, exerciseLookup]);

  const setExerciseId = (
    weekIdx: number,
    dayIdx: number,
    exIdx: number,
    newId: string | null,
  ) => {
    setProgram((p) => {
      const next = structuredClone(p);
      next.weeks[weekIdx].days[dayIdx].exercises[exIdx].exerciseId = newId;
      return next;
    });
  };

  const removeExercise = (weekIdx: number, dayIdx: number, exIdx: number) => {
    setProgram((p) => {
      const next = structuredClone(p);
      next.weeks[weekIdx].days[dayIdx].exercises.splice(exIdx, 1);
      next.weeks[weekIdx].days[dayIdx].exercises.forEach(
        (ex: AIExercise, i: number) => {
          ex.order = i;
        },
      );
      return next;
    });
  };

  const handleApply = async () => {
    if (unresolvedCount > 0) {
      setError(
        `Resolve all ${unresolvedCount} unknown exercise(s) before applying.`,
      );
      return;
    }
    setError(null);
    setApplying(true);
    try {
      const res = await fetch('/api/ai/apply', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({
          generationId,
          program,
          startDate,
          isActive: activate,
        }),
      });
      const body = await res.json();
      if (!res.ok) throw new Error(body.error ?? `HTTP ${res.status}`);
      onApplied(body.programId);
    } catch (e) {
      setError((e as Error).message);
    } finally {
      setApplying(false);
    }
  };

  return (
    <div className="bg-zinc-900 border border-zinc-800 rounded p-4 space-y-4">
      <div>
        <h3 className="text-lg font-bold text-white">{program.name}</h3>
        <p className="text-xs text-zinc-500 mt-1">
          {program.type} · {program.durationWeeks} week
          {program.durationWeeks === 1 ? '' : 's'} · {program.weeks.length}{' '}
          week{program.weeks.length === 1 ? '' : 's'} planned
        </p>
        {program.description && (
          <p className="text-sm text-zinc-300 mt-2">{program.description}</p>
        )}
      </div>

      {unresolvedCount > 0 && (
        <div className="rounded bg-amber-950/30 border border-amber-900 px-3 py-2 text-xs text-amber-200">
          {unresolvedCount} exercise(s) the AI couldn&apos;t map to your
          library. Pick a replacement or remove them before applying.
        </div>
      )}

      <div className="space-y-3">
        {program.weeks.map((w, wIdx) => (
          <details
            key={w.weekNumber}
            open={wIdx === 0}
            className="bg-zinc-950 border border-zinc-800 rounded"
          >
            <summary className="cursor-pointer px-3 py-2 text-sm text-white">
              Week {w.weekNumber}
              {w.phase && <span className="text-zinc-500"> · {w.phase}</span>}
              <span className="text-zinc-600 text-xs">
                {' '}
                ({w.days.length} day{w.days.length === 1 ? '' : 's'})
              </span>
            </summary>
            <div className="p-3 space-y-2">
              {w.days.map((d, dIdx) => (
                <div
                  key={d.dayOfWeek}
                  className="bg-zinc-900 border border-zinc-800 rounded p-3"
                >
                  <p className="text-xs font-semibold text-zinc-300 uppercase tracking-wider">
                    {DAY_LABELS[d.dayOfWeek]}
                    {d.name && (
                      <span className="text-zinc-500 normal-case font-normal">
                        {' '}
                        · {d.name}
                      </span>
                    )}
                  </p>
                  <ul className="mt-2 space-y-2">
                    {d.exercises.map((ex, eIdx) => {
                      const isUnknown =
                        !ex.exerciseId || !exerciseLookup.has(ex.exerciseId);
                      const lib = ex.exerciseId
                        ? exerciseLookup.get(ex.exerciseId)
                        : null;
                      return (
                        <li
                          key={eIdx}
                          className={`text-sm ${
                            isUnknown
                              ? 'bg-amber-950/30 border border-amber-900'
                              : 'bg-zinc-950 border border-zinc-800'
                          } rounded p-2`}
                        >
                          <div className="flex items-start justify-between gap-2">
                            <div className="min-w-0 flex-1">
                              <div className="text-white">
                                {lib?.name ?? ex.exerciseName}
                                {isUnknown && (
                                  <span className="ml-2 text-[10px] uppercase tracking-wider text-amber-400">
                                    not in library
                                  </span>
                                )}
                              </div>
                              {(ex.sets || ex.repsMin || ex.repsMax || ex.rpe || ex.restSeconds || ex.suggestedWeight) && (
                                <div className="text-xs text-zinc-500 mt-0.5">
                                  {ex.sets ? `${ex.sets}×` : ''}
                                  {ex.repsMin === ex.repsMax || !ex.repsMax
                                    ? (ex.repsMin ?? '?')
                                    : `${ex.repsMin}-${ex.repsMax}`}
                                  {ex.suggestedWeight != null && (
                                    <> @ {ex.suggestedWeight}{ex.suggestedWeightUnit ?? ''}</>
                                  )}
                                  {ex.rpe ? ` @ RPE ${ex.rpe}` : ''}
                                  {ex.restSeconds ? ` · rest ${ex.restSeconds}s` : ''}
                                </div>
                              )}
                              {ex.notes && (
                                <div className="text-xs text-zinc-400 mt-1 italic">
                                  {ex.notes}
                                </div>
                              )}
                            </div>
                            <button
                              type="button"
                              onClick={() => removeExercise(wIdx, dIdx, eIdx)}
                              className="text-xs text-red-400 hover:text-red-300 px-1"
                              title="Remove from program"
                            >
                              ✕
                            </button>
                          </div>
                          {isUnknown && (
                            <div className="mt-2">
                              <select
                                value={ex.exerciseId ?? ''}
                                onChange={(e) =>
                                  setExerciseId(wIdx, dIdx, eIdx, e.target.value || null)
                                }
                                className="w-full text-xs px-2 py-1 rounded border border-amber-900 bg-zinc-900 text-white"
                              >
                                <option value="">
                                  Map to existing exercise…
                                </option>
                                {exercises.map((opt) => (
                                  <option key={opt.id} value={opt.id}>
                                    {opt.name} ({opt.type})
                                  </option>
                                ))}
                              </select>
                            </div>
                          )}
                        </li>
                      );
                    })}
                  </ul>
                </div>
              ))}
            </div>
          </details>
        ))}
      </div>

      <div className="border-t border-zinc-800 pt-4 space-y-3">
        <div className="grid grid-cols-2 gap-3">
          <label className="block">
            <span className="text-[11px] font-semibold text-zinc-400 uppercase tracking-wider block mb-1">
              Start date
            </span>
            <input
              type="date"
              value={startDate}
              onChange={(e) => setStartDate(e.target.value)}
              className="w-full px-3 py-2 text-sm rounded border border-zinc-700 bg-zinc-800 text-white"
            />
          </label>
          <label className="flex items-end gap-2">
            <input
              type="checkbox"
              checked={activate}
              onChange={(e) => setActivate(e.target.checked)}
              className="mb-2"
            />
            <span className="text-xs text-zinc-300 mb-2">
              Activate this program after applying
            </span>
          </label>
        </div>

        {error && (
          <div className="rounded bg-red-900/50 px-3 py-2 border border-red-800 text-xs text-red-400">
            {error}
          </div>
        )}

        <button
          type="button"
          onClick={handleApply}
          disabled={applying || unresolvedCount > 0}
          className="px-5 py-2 rounded bg-emerald-700 text-white font-bold text-xs uppercase tracking-wider hover:bg-emerald-600 disabled:bg-zinc-700 disabled:text-zinc-500"
        >
          {applying ? (
            <>
              <Loader2 className="inline w-4 h-4 animate-spin mr-2" />
              Applying…
            </>
          ) : (
            'Apply this program'
          )}
        </button>
      </div>
    </div>
  );
}

function PartialTree({ partial }: { partial: Partial<AIProgram> }) {
  const weeks = (partial.weeks as AIWeek[] | undefined) ?? [];
  return (
    <div className="bg-zinc-950 border border-zinc-800 rounded p-3 space-y-2">
      <div className="text-xs">
        {partial.name && (
          <span className="text-white font-semibold">{partial.name}</span>
        )}
        {partial.type && (
          <span className="text-zinc-500"> · {partial.type}</span>
        )}
        {typeof partial.durationWeeks === 'number' && (
          <span className="text-zinc-500"> · {partial.durationWeeks} wk</span>
        )}
      </div>
      {weeks.length > 0 && (
        <ul className="text-xs text-zinc-300 space-y-1">
          {weeks.map((w, i) => (
            <li key={i}>
              <span className="text-zinc-500">Week {w?.weekNumber ?? '?'}:</span>{' '}
              {Array.isArray(w?.days)
                ? `${w.days.length} day${w.days.length === 1 ? '' : 's'} (${w.days.reduce(
                    (n: number, d: AIDay) =>
                      n + (Array.isArray(d?.exercises) ? d.exercises.length : 0),
                    0,
                  )} exercises)`
                : '…'}
              {w?.phase && <span className="text-zinc-500"> · {w.phase}</span>}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

function StatusPill({ status }: { status: string }) {
  const map: Record<string, { color: string; label: string }> = {
    pending: { color: 'text-zinc-400 bg-zinc-800', label: 'pending' },
    completed: { color: 'text-emerald-400 bg-emerald-950', label: 'completed' },
    applied: { color: 'text-emerald-400 bg-emerald-950', label: 'applied' },
    failed: { color: 'text-red-400 bg-red-950', label: 'failed' },
  };
  const m = map[status] ?? map.pending;
  return (
    <span
      className={`inline-flex items-center gap-1 ${m.color} rounded px-2 py-0.5 text-[10px]`}
    >
      {m.label}
    </span>
  );
}

function formatDuration(ms: number): string {
  if (ms < 1000) return `${ms}ms`;
  if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`;
  const m = Math.floor(ms / 60_000);
  const s = Math.round((ms % 60_000) / 1000);
  return `${m}m ${s}s`;
}
