import { useCallback, useEffect, useMemo, useState } from "react";
import "./App.css";
import {
  cancelChunkJob,
  createChunkJob,
  getAssets,
  getChunkOutputFiles,
  scanMediaFolder,
} from "./api";
import { ChunkGrid } from "./components/ChunkGrid";
import { ConfigPanel } from "./components/ConfigPanel";
import { ErrorLog } from "./components/ErrorLog";
import { OutputFiles } from "./components/OutputFiles";
import { QueueGauge } from "./components/QueueGauge";
import { StatsPanel } from "./components/StatsPanel";
import { WorkerPanel } from "./components/WorkerPanel";
import { useGrpcStream } from "./hooks/useGrpcStream";
import type {
  ChunkInfo,
  ChunkOutputFile,
  ErrorEntry,
  MediaAsset,
  PipelineConfig,
  PipelineStats,
  WorkerInfo,
} from "./types";

export default function App() {
  const [jobId, setJobId] = useState<string | null>(null);
  const [celeryTaskId, setCeleryTaskId] = useState<string | null>(null);
  const [running, setRunning] = useState(false);
  const [error, setError] = useState<string | null>(null);

  // Asset state
  const [assets, setAssets] = useState<MediaAsset[]>([]);
  const [selectedAsset, setSelectedAsset] = useState<MediaAsset | null>(null);
  const [scanning, setScanning] = useState(false);

  // Output files
  const [outputFiles, setOutputFiles] = useState<ChunkOutputFile[]>([]);

  const {
    events,
    connected,
    done,
    reset: resetStream,
  } = useGrpcStream(jobId);

  // Load assets on mount
  useEffect(() => {
    getAssets()
      .then((data) =>
        setAssets(data.sort((a, b) => a.filename.localeCompare(b.filename))),
      )
      .catch((e) =>
        setError(e instanceof Error ? e.message : "Failed to load assets"),
      );
  }, []);

  // Fetch output files when job completes
  useEffect(() => {
    if (done && jobId) {
      getChunkOutputFiles(jobId)
        .then(setOutputFiles)
        .catch(() => setOutputFiles([]));
    }
  }, [done, jobId]);

  const handleScan = useCallback(async () => {
    setScanning(true);
    setError(null);
    try {
      await scanMediaFolder();
      const data = await getAssets();
      setAssets(data.sort((a, b) => a.filename.localeCompare(b.filename)));
    } catch (e) {
      setError(e instanceof Error ? e.message : "Scan failed");
    } finally {
      setScanning(false);
    }
  }, []);

  // Derive state from raw events
  const { chunks, workers, stats, errors, queueSize } =
    useMemo(() => {
      const chunkMap = new Map<number, ChunkInfo>();
      const workerMap = new Map<string, WorkerInfo>();
      const errorList: ErrorEntry[] = [];
      let totalChunks = 0;
      let processed = 0;
      let failed = 0;
      let retries = 0;
      let elapsed = 0;
      let throughput = 0;
      let queueSize = 0;
      let pipelineDone = false;

      for (const evt of events) {
        const evtType = evt.event_type || "";

        if (evt.total_chunks) totalChunks = evt.total_chunks;
        if (evt.processed_chunks) processed = evt.processed_chunks;
        if (evt.failed_chunks) failed = evt.failed_chunks;
        if (evt.elapsed) elapsed = evt.elapsed;
        if (evt.throughput_mbps) throughput = evt.throughput_mbps;
        if (evt.queue_size !== undefined) queueSize = evt.queue_size;

        if (evtType === "pipeline_complete" || evtType === "pipeline_error") {
          pipelineDone = true;
          queueSize = 0;
        }

        // Track chunks by raw event type
        if (evt.sequence !== undefined) {
          const existing = chunkMap.get(evt.sequence) || {
            sequence: evt.sequence,
            state: "pending" as const,
          };

          if (evtType === "chunk_queued") {
            existing.state = "queued";
          } else if (evtType === "chunk_processing") {
            existing.state = "processing";
            if (evt.worker_id) existing.worker_id = evt.worker_id;
          } else if (evtType === "chunk_done") {
            existing.state = "done";
            if (evt.processing_time)
              existing.processing_time = evt.processing_time;
            if (evt.retries) existing.retries = evt.retries;
          } else if (evtType === "chunk_error") {
            existing.state = "error";
            if (evt.error) existing.error = evt.error;
          } else if (evtType === "chunk_retry") {
            existing.state = "retry";
            if (evt.retries) existing.retries = evt.retries;
          }

          if (evt.size) existing.size = evt.size;
          chunkMap.set(evt.sequence, existing);
        }

        // Track workers from worker_status events
        if (evt.worker_id && evtType === "worker_status") {
          const w = workerMap.get(evt.worker_id) || {
            worker_id: evt.worker_id,
            state: "idle" as const,
            processed: 0,
            errors: 0,
            retries: 0,
          };

          if (evt.state === "processing") {
            w.state = "processing";
            w.current_chunk = evt.sequence;
          } else if (evt.state === "idle") {
            w.state = "idle";
            w.current_chunk = undefined;
          } else if (evt.state === "stopped") {
            w.state = "stopped";
            w.current_chunk = undefined;
          }

          workerMap.set(evt.worker_id, w);
        }

        // Also update workers from chunk lifecycle events
        if (
          evt.worker_id &&
          (evtType === "chunk_processing" ||
            evtType === "chunk_done" ||
            evtType === "chunk_error")
        ) {
          const w = workerMap.get(evt.worker_id) || {
            worker_id: evt.worker_id,
            state: "idle" as const,
            processed: 0,
            errors: 0,
            retries: 0,
          };

          if (evtType === "chunk_processing") {
            w.state = "processing";
            w.current_chunk = evt.sequence;
          } else if (evtType === "chunk_done") {
            w.processed++;
            w.state = "idle";
            w.current_chunk = undefined;
          } else if (evtType === "chunk_error") {
            w.errors++;
          }

          if (evt.retries) {
            retries += evt.retries;
            w.retries += evt.retries;
          }

          workerMap.set(evt.worker_id, w);
        }

        // Track errors
        if (evt.error) {
          errorList.push({
            timestamp: Date.now(),
            sequence: evt.sequence,
            worker_id: evt.worker_id,
            error: evt.error,
            retries: evt.retries,
            event_type: evtType,
          });
        }
      }

      // When pipeline is done, mark all workers as stopped
      if (pipelineDone) {
        for (const w of workerMap.values()) {
          w.state = "stopped";
          w.current_chunk = undefined;
        }
      }

      const statsObj: PipelineStats = {
        total_chunks: totalChunks,
        processed,
        failed,
        retries,
        elapsed,
        throughput_mbps: throughput,
        queue_size: queueSize,
      };

      return {
        chunks: Array.from(chunkMap.values()).sort(
          (a, b) => a.sequence - b.sequence,
        ),
        workers: Array.from(workerMap.values()),
        stats: statsObj,
        errors: errorList,
        queueSize,
      };
    }, [events]);

  const handleStart = useCallback(async (config: PipelineConfig) => {
    setError(null);
    setRunning(true);
    setOutputFiles([]);
    try {
      const result = await createChunkJob(config);
      setJobId(result.id);
      setCeleryTaskId(result.celery_task_id);
    } catch (e) {
      setError(e instanceof Error ? e.message : "Failed to start");
      setRunning(false);
    }
  }, []);

  const handleStop = useCallback(async () => {
    if (!celeryTaskId) {
      setError("No task ID to cancel");
      return;
    }
    try {
      const result = await cancelChunkJob(celeryTaskId);
      if (result.ok) {
        resetStream();
        setRunning(false);
        setError(null);
      } else {
        setError(result.message || "Failed to cancel");
      }
    } catch (e) {
      setError(e instanceof Error ? e.message : "Failed to cancel");
    }
  }, [celeryTaskId, resetStream]);

  const handleReset = useCallback(() => {
    setJobId(null);
    setCeleryTaskId(null);
    setRunning(false);
    setError(null);
    setOutputFiles([]);
    resetStream();
  }, [resetStream]);

  // Reset running state when done
  if (done && running) {
    setRunning(false);
  }

  return (
    <div className="app">
      <header className="header">
        <h1>MPR Chunker Pipeline</h1>
        <div className="connection-status">
          {jobId && (
            <span className={`dot ${connected ? "connected" : ""}`} />
          )}
          <span className="status-text">
            {!jobId
              ? "Configure and launch"
              : connected
                ? "Streaming"
                : done
                  ? "Complete"
                  : "Connecting..."}
          </span>
        </div>
      </header>

      {error && <div className="error-banner">{error}</div>}

      <div className="layout">
        <aside className="sidebar">
          <ConfigPanel
            onStart={handleStart}
            onStop={handleStop}
            onReset={handleReset}
            running={running}
            done={done}
            assets={assets}
            selectedAsset={selectedAsset}
            onSelectAsset={setSelectedAsset}
            onScan={handleScan}
            scanning={scanning}
          />
        </aside>

        <main className="main">
          <div className="main-grid">
            <div className="main-left">
              <ChunkGrid chunks={chunks} totalChunks={stats.total_chunks} />
              <QueueGauge current={queueSize} max={10} buffered={0} />
              {done && outputFiles.length > 0 && (
                <OutputFiles files={outputFiles} />
              )}
            </div>
            <div className="main-right">
              <WorkerPanel workers={workers} />
              <StatsPanel stats={stats} />
              <ErrorLog errors={errors} />
            </div>
          </div>
        </main>
      </div>
    </div>
  );
}
