import { useState, useCallback } from 'react';
import { autoDetectMultiFrame, type DetectOptions } from '@core/detector.js';
import { attackReencode, attackDownscale, attackBrightness, attackContrast, attackSaturation, attackCrop } from '../lib/video-io.js';

type TestStatus = 'idle' | 'running' | 'pass' | 'fail' | 'error';

interface TestResult {
  status: TestStatus;
  confidence?: number;
  payloadMatch?: boolean;
}

interface RobustnessTestProps {
  blob: Blob;
  width: number;
  height: number;
  payload: string;
  secretKey: string;
}

interface TestDef {
  label: string;
  category: string;
  run: (blob: Blob, w: number, h: number) => Promise<{ yPlanes: Uint8Array[]; width: number; height: number }>;
  detectOptions?: DetectOptions;
}

const TESTS: TestDef[] = [
  // CRF re-encoding
  { label: 'CRF 23', category: 'Re-encode', run: (b, w, h) => attackReencode(b, 23, w, h) },
  { label: 'CRF 28', category: 'Re-encode', run: (b, w, h) => attackReencode(b, 28, w, h) },
  { label: 'CRF 33', category: 'Re-encode', run: (b, w, h) => attackReencode(b, 33, w, h) },
  { label: 'CRF 38', category: 'Re-encode', run: (b, w, h) => attackReencode(b, 38, w, h) },
  { label: 'CRF 43', category: 'Re-encode', run: (b, w, h) => attackReencode(b, 43, w, h) },
  // Downscale
  { label: '25%', category: 'Downscale', run: (b, w, h) => attackDownscale(b, 25, w, h) },
  { label: '50%', category: 'Downscale', run: (b, w, h) => attackDownscale(b, 50, w, h) },
  { label: '75%', category: 'Downscale', run: (b, w, h) => attackDownscale(b, 75, w, h) },
  { label: '90%', category: 'Downscale', run: (b, w, h) => attackDownscale(b, 90, w, h) },
  // Brightness
  { label: '-0.2', category: 'Brightness', run: (b, w, h) => attackBrightness(b, -0.2, w, h) },
  { label: '+0.2', category: 'Brightness', run: (b, w, h) => attackBrightness(b, 0.2, w, h) },
  { label: '+0.4', category: 'Brightness', run: (b, w, h) => attackBrightness(b, 0.4, w, h) },
  // Contrast
  { label: '0.5x', category: 'Contrast', run: (b, w, h) => attackContrast(b, 0.5, w, h) },
  { label: '1.5x', category: 'Contrast', run: (b, w, h) => attackContrast(b, 1.5, w, h) },
  { label: '2.0x', category: 'Contrast', run: (b, w, h) => attackContrast(b, 2.0, w, h) },
  // Saturation
  { label: '0x', category: 'Saturation', run: (b, w, h) => attackSaturation(b, 0, w, h) },
  { label: '0.5x', category: 'Saturation', run: (b, w, h) => attackSaturation(b, 0.5, w, h) },
  { label: '2.0x', category: 'Saturation', run: (b, w, h) => attackSaturation(b, 2.0, w, h) },
  // Crop (~5-10% from edges)
  { label: '5%', category: 'Crop', run: (b, w, h) => {
    const px = Math.round(Math.min(w, h) * 0.05);
    return attackCrop(b, px, px, px, px, w, h);
  }, detectOptions: { cropResilient: true } },
  { label: '10%', category: 'Crop', run: (b, w, h) => {
    const px = Math.round(Math.min(w, h) * 0.10);
    return attackCrop(b, px, px, px, px, w, h);
  }, detectOptions: { cropResilient: true } },
  { label: '15%', category: 'Crop', run: (b, w, h) => {
    const px = Math.round(Math.min(w, h) * 0.15);
    return attackCrop(b, px, px, px, px, w, h);
  }, detectOptions: { cropResilient: true } },
  { label: '20%', category: 'Crop', run: (b, w, h) => {
    const px = Math.round(Math.min(w, h) * 0.20);
    return attackCrop(b, px, px, px, px, w, h);
  }, detectOptions: { cropResilient: true } },
];

function payloadToHex(payload: Uint8Array): string {
  return Array.from(payload).map((b) => b.toString(16).padStart(2, '0')).join('').toUpperCase();
}

export default function RobustnessTest({ blob, width, height, payload, secretKey }: RobustnessTestProps) {
  const [results, setResults] = useState<Record<number, TestResult>>({});
  const [runningAll, setRunningAll] = useState(false);
  const [progress, setProgress] = useState(0);

  const expectedHex = payload.replace(/^0x/, '').toUpperCase();

  const runTest = useCallback(async (idx: number) => {
    setResults((prev) => ({ ...prev, [idx]: { status: 'running' } }));
    try {
      const test = TESTS[idx];
      const attacked = await test.run(blob, width, height);

      // Pipeline already caps at 30 frames; use up to 10 evenly spaced
      const step = Math.max(1, Math.floor(attacked.yPlanes.length / 10));
      const sampled = attacked.yPlanes.filter((_, i) => i % step === 0).slice(0, 10);

      const detection = autoDetectMultiFrame(sampled, attacked.width, attacked.height, secretKey, test.detectOptions);

      const detectedHex = detection.payload ? payloadToHex(detection.payload) : '';
      const match = detection.detected && detectedHex === expectedHex;

      setResults((prev) => ({
        ...prev,
        [idx]: {
          status: match ? 'pass' : 'fail',
          confidence: detection.confidence,
          payloadMatch: match,
        },
      }));
    } catch (e) {
      console.error(`Robustness test ${idx} error:`, e);
      setResults((prev) => ({ ...prev, [idx]: { status: 'error' } }));
    }
  }, [blob, width, height, secretKey, expectedHex]);

  const runAll = useCallback(async () => {
    setRunningAll(true);
    setProgress(0);
    for (let i = 0; i < TESTS.length; i++) {
      await runTest(i);
      setProgress((i + 1) / TESTS.length);
    }
    setRunningAll(false);
  }, [runTest]);

  const categories = ['Re-encode', 'Downscale', 'Brightness', 'Contrast', 'Saturation', 'Crop'];

  return (
    <div className="space-y-4">
      <div className="flex items-center justify-between">
        <h4 className="text-sm font-semibold text-zinc-300">Robustness Testing</h4>
        <button
          onClick={runAll}
          disabled={runningAll}
          className="rounded-lg bg-zinc-800 px-3 py-1.5 text-xs font-medium text-zinc-300
            ring-1 ring-zinc-700 transition-all hover:bg-zinc-700 hover:text-zinc-100
            disabled:cursor-not-allowed disabled:opacity-50"
        >
          {runningAll ? (
            <span className="flex items-center gap-1.5">
              <span className="h-3 w-3 animate-spin rounded-full border-[1.5px] border-zinc-500 border-t-zinc-200" />
              {Math.round(progress * 100)}%
            </span>
          ) : (
            'Run All Tests'
          )}
        </button>
      </div>

      {runningAll && (
        <div className="h-1 w-full overflow-hidden rounded-full bg-zinc-800">
          <div
            className="h-full rounded-full bg-zinc-500 transition-all duration-300"
            style={{ width: `${progress * 100}%` }}
          />
        </div>
      )}

      <div className="space-y-3">
        {categories.map((cat) => {
          const catTests = TESTS.map((t, i) => ({ ...t, idx: i })).filter((t) => t.category === cat);
          return (
            <div key={cat} className="flex items-center gap-2">
              <span className="w-24 shrink-0 text-xs text-zinc-500">{cat}</span>
              <div className="flex flex-wrap gap-1.5">
                {catTests.map(({ label, idx }) => {
                  const r = results[idx];
                  const status = r?.status ?? 'idle';
                  return (
                    <button
                      key={idx}
                      onClick={() => runTest(idx)}
                      disabled={status === 'running' || runningAll}
                      className={`inline-flex items-center gap-1 rounded-md px-2.5 py-1 text-xs font-medium
                        ring-1 transition-all disabled:cursor-not-allowed
                        ${status === 'idle' ? 'bg-zinc-800/80 text-zinc-400 ring-zinc-700 hover:bg-zinc-700 hover:text-zinc-200' : ''}
                        ${status === 'running' ? 'bg-zinc-800 text-zinc-400 ring-zinc-700' : ''}
                        ${status === 'pass' ? 'bg-emerald-950/40 text-emerald-400 ring-emerald-800/50' : ''}
                        ${status === 'fail' ? 'bg-red-950/40 text-red-400 ring-red-800/50' : ''}
                        ${status === 'error' ? 'bg-amber-950/40 text-amber-400 ring-amber-800/50' : ''}
                      `}
                    >
                      {status === 'running' && (
                        <span className="h-3 w-3 animate-spin rounded-full border-[1.5px] border-zinc-500 border-t-zinc-200" />
                      )}
                      {status === 'pass' && <span>&#x2705;</span>}
                      {status === 'fail' && <span>&#x274C;</span>}
                      {status === 'error' && <span>&#x26A0;&#xFE0F;</span>}
                      {label}
                      {r?.confidence !== undefined && (
                        <span className="ml-0.5 opacity-70">{(r.confidence * 100).toFixed(0)}%</span>
                      )}
                    </button>
                  );
                })}
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}
