import { useState, useCallback, useEffect, useRef } from "react";
import { useMutation } from "@tanstack/react-query";
import { Button } from "@/components/ui/button";
import { Slider } from "@/components/ui/slider";
import { Label } from "@/components/ui/label";
import { Card, CardContent } from "@/components/ui/card";
import { useToast } from "@/hooks/use-toast";
import { FileUploadZone } from "@/components/FileUploadZone";
import { WaveformVisualizer } from "@/components/WaveformVisualizer";
import { ParameterAnalysisPanel } from "@/components/ParameterAnalysisPanel";
import { ProcessingPipeline } from "@/components/ProcessingPipeline";
import { ComparisonPlayer } from "@/components/ComparisonPlayer";
import { apiRequest, queryClient } from "@/lib/queryClient";
import { Wand2, RotateCcw, Loader2, Sliders } from "lucide-react";
import type { TransformJob, UploadResponse } from "@shared/schema";

interface UploadedFile {
  file: File;
  id: string;
  url: string;
  duration: number;
  waveformData: number[];
}

export default function VoiceTransformer() {
  const { toast } = useToast();
  const [sourceFile, setSourceFile] = useState<UploadedFile | null>(null);
  const [targetFile, setTargetFile] = useState<UploadedFile | null>(null);
  const [currentJobId, setCurrentJobId] = useState<string | null>(null);
  const [job, setJob] = useState<TransformJob | null>(null);
  const [resultUrl, setResultUrl] = useState<string | null>(null);
  const [resultWaveform, setResultWaveform] = useState<number[] | null>(null);
  const [isSourceLoading, setIsSourceLoading] = useState(false);
  const [isTargetLoading, setIsTargetLoading] = useState(false);
  const [intensity, setIntensity] = useState<number>(100);
  
  const wsRef = useRef<WebSocket | null>(null);
  const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
  const lastJobStatusRef = useRef<string | null>(null);
  const waveformFetchedRef = useRef<boolean>(false);

  const connectWebSocket = useCallback((jobId: string) => {
    if (wsRef.current?.readyState === WebSocket.OPEN) {
      wsRef.current.send(JSON.stringify({ type: "subscribe", jobId }));
      return;
    }

    const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
    const ws = new WebSocket(`${protocol}//${window.location.host}/ws`);
    
    ws.onopen = () => {
      ws.send(JSON.stringify({ type: "subscribe", jobId }));
    };

    ws.onmessage = (event) => {
      try {
        const message = JSON.parse(event.data);
        if (message.type === "job_update" && message.data) {
          const jobData = message.data as TransformJob;
          lastJobStatusRef.current = jobData.status;
          setJob(jobData);
          
          if (jobData.status === "completed" && jobData.resultUrl && !waveformFetchedRef.current) {
            waveformFetchedRef.current = true;
            setResultUrl(jobData.resultUrl);
            fetch(`/api/waveform/${jobData.id}`)
              .then((res) => res.json())
              .then((data) => setResultWaveform(data.waveformData))
              .catch(console.error);
          }
        }
      } catch (error) {
        console.error("WebSocket message parse error:", error);
      }
    };

    ws.onclose = () => {
      const isFinalState = lastJobStatusRef.current === "completed" || lastJobStatusRef.current === "error";
      if (currentJobId && !isFinalState) {
        reconnectTimeoutRef.current = setTimeout(() => {
          connectWebSocket(jobId);
        }, 1000);
      }
    };

    ws.onerror = (error) => {
      console.error("WebSocket error:", error);
    };

    wsRef.current = ws;
  }, [currentJobId]);

  useEffect(() => {
    if (currentJobId) {
      connectWebSocket(currentJobId);
    }

    return () => {
      if (reconnectTimeoutRef.current) {
        clearTimeout(reconnectTimeoutRef.current);
      }
      if (wsRef.current) {
        wsRef.current.close();
        wsRef.current = null;
      }
    };
  }, [currentJobId, connectWebSocket]);

  const uploadMutation = useMutation({
    mutationFn: async ({ file, type }: { file: File; type: "source" | "target" }) => {
      const formData = new FormData();
      formData.append("audio", file);
      formData.append("type", type);

      const response = await fetch("/api/upload", {
        method: "POST",
        body: formData,
      });

      if (!response.ok) {
        throw new Error("Upload failed");
      }

      return response.json() as Promise<UploadResponse>;
    },
    onSuccess: (data, variables) => {
      const uploadedFile: UploadedFile = {
        file: variables.file,
        id: data.fileId,
        url: `/api/audio/${data.fileId}`,
        duration: data.duration,
        waveformData: data.waveformData,
      };

      if (variables.type === "source") {
        setSourceFile(uploadedFile);
        setIsSourceLoading(false);
      } else {
        setTargetFile(uploadedFile);
        setIsTargetLoading(false);
      }

      toast({
        title: "File uploaded",
        description: `${variables.file.name} has been processed successfully.`,
      });
    },
    onError: (error, variables) => {
      if (variables.type === "source") {
        setIsSourceLoading(false);
      } else {
        setIsTargetLoading(false);
      }
      toast({
        title: "Upload failed",
        description: "Please try again with a valid MP3 file.",
        variant: "destructive",
      });
    },
  });

  const transformMutation = useMutation({
    mutationFn: async () => {
      if (!sourceFile || !targetFile) {
        throw new Error("Both files are required");
      }

      const response = await apiRequest("POST", "/api/transform", {
        sourceFileId: sourceFile.id,
        targetFileId: targetFile.id,
        intensity,
      });

      const data = await response.json();
      return data as TransformJob;
    },
    onSuccess: (data) => {
      setCurrentJobId(data.id);
      setJob(data);
      toast({
        title: "Transformation started",
        description: "Your voice transformation is being processed.",
      });
    },
    onError: () => {
      toast({
        title: "Transformation failed",
        description: "Unable to start the transformation process.",
        variant: "destructive",
      });
    },
  });

  const handleSourceSelect = useCallback(
    (file: File) => {
      setIsSourceLoading(true);
      uploadMutation.mutate({ file, type: "source" });
    },
    [uploadMutation]
  );

  const handleTargetSelect = useCallback(
    (file: File) => {
      setIsTargetLoading(true);
      uploadMutation.mutate({ file, type: "target" });
    },
    [uploadMutation]
  );

  const handleSourceRemove = useCallback(() => {
    setSourceFile(null);
    setCurrentJobId(null);
    setJob(null);
    setResultUrl(null);
    setResultWaveform(null);
  }, []);

  const handleTargetRemove = useCallback(() => {
    setTargetFile(null);
    setCurrentJobId(null);
    setJob(null);
    setResultUrl(null);
    setResultWaveform(null);
  }, []);

  const handleTransform = useCallback(() => {
    setResultUrl(null);
    setResultWaveform(null);
    setJob(null);
    lastJobStatusRef.current = null;
    waveformFetchedRef.current = false;
    transformMutation.mutate();
  }, [transformMutation]);

  const handleReset = useCallback(() => {
    setSourceFile(null);
    setTargetFile(null);
    setCurrentJobId(null);
    setJob(null);
    setResultUrl(null);
    setResultWaveform(null);
    setIntensity(100);
    lastJobStatusRef.current = null;
    waveformFetchedRef.current = false;
    if (wsRef.current) {
      wsRef.current.close();
      wsRef.current = null;
    }
    queryClient.invalidateQueries({ queryKey: ["/api/transform"] });
  }, []);

  const handleDownload = useCallback(() => {
    if (resultUrl) {
      const link = document.createElement("a");
      link.href = resultUrl;
      link.download = `transformed_${Date.now()}.mp3`;
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
    }
  }, [resultUrl]);

  const canTransform = sourceFile && targetFile && !transformMutation.isPending;
  const isProcessing = job ? !["completed", "error", "pending"].includes(job.status) : false;

  return (
    <div className="min-h-screen bg-background">
      <div className="max-w-6xl mx-auto px-4 py-8 space-y-8">
        <section className="grid grid-cols-1 lg:grid-cols-2 gap-6">
          <FileUploadZone
            label="Source Voice (Template)"
            description="The voice characteristics to copy"
            type="source"
            file={sourceFile?.file || null}
            isLoading={isSourceLoading}
            duration={sourceFile?.duration}
            onFileSelect={handleSourceSelect}
            onRemove={handleSourceRemove}
          />
          <FileUploadZone
            label="Target Voice (To Transform)"
            description="The voice that will be transformed"
            type="target"
            file={targetFile?.file || null}
            isLoading={isTargetLoading}
            duration={targetFile?.duration}
            onFileSelect={handleTargetSelect}
            onRemove={handleTargetRemove}
          />
        </section>

        {sourceFile && targetFile && (
          <section className="max-w-md mx-auto">
            <Card>
              <CardContent className="pt-6">
                <div className="space-y-4">
                  <div className="flex items-center gap-2">
                    <Sliders className="h-4 w-4 text-muted-foreground" />
                    <Label htmlFor="intensity-slider" className="text-sm font-medium">
                      Transformation Intensity
                    </Label>
                  </div>
                  <div className="flex items-center gap-4">
                    <Slider
                      id="intensity-slider"
                      value={[intensity]}
                      onValueChange={(values) => setIntensity(values[0])}
                      min={0}
                      max={100}
                      step={1}
                      disabled={isProcessing}
                      className="flex-1"
                      data-testid="slider-intensity"
                    />
                    <span className="text-sm font-mono w-12 text-right text-muted-foreground" data-testid="text-intensity-value">
                      {intensity}%
                    </span>
                  </div>
                  <p className="text-xs text-muted-foreground">
                    {intensity === 0 && "No transformation - output will match original target voice"}
                    {intensity > 0 && intensity < 30 && "Subtle transformation - slight adjustment toward source voice"}
                    {intensity >= 30 && intensity < 70 && "Moderate transformation - balanced blend of both voices"}
                    {intensity >= 70 && intensity < 100 && "Strong transformation - closely matching source voice"}
                    {intensity === 100 && "Full transformation - maximum matching to source voice"}
                  </p>
                </div>
              </CardContent>
            </Card>
          </section>
        )}

        <section className="flex flex-col sm:flex-row items-center justify-center gap-4">
          <Button
            size="lg"
            onClick={handleTransform}
            disabled={!canTransform || isProcessing}
            className="min-w-[200px]"
            data-testid="button-transform"
          >
            {isProcessing ? (
              <>
                <Loader2 className="h-5 w-5 mr-2 animate-spin" />
                Processing...
              </>
            ) : (
              <>
                <Wand2 className="h-5 w-5 mr-2" />
                Transform Voice
              </>
            )}
          </Button>
          <Button
            variant="secondary"
            size="lg"
            onClick={handleReset}
            disabled={isProcessing}
            data-testid="button-reset"
          >
            <RotateCcw className="h-5 w-5 mr-2" />
            Reset All
          </Button>
        </section>

        {(sourceFile || targetFile || job) && (
          <>
            <section className="grid grid-cols-1 lg:grid-cols-3 gap-6">
              <WaveformVisualizer
                label="Source Voice"
                type="source"
                audioUrl={sourceFile?.url}
                waveformData={sourceFile?.waveformData}
              />
              <WaveformVisualizer
                label="Target Voice"
                type="target"
                audioUrl={targetFile?.url}
                waveformData={targetFile?.waveformData}
              />
              <WaveformVisualizer
                label="Transformed Result"
                type="result"
                audioUrl={resultUrl || undefined}
                waveformData={resultWaveform || undefined}
                showDownload={!!resultUrl}
                onDownload={handleDownload}
              />
            </section>

            <section className="grid grid-cols-1 lg:grid-cols-2 gap-6">
              <ProcessingPipeline job={job || undefined} />
              <ParameterAnalysisPanel
                sourceParams={job?.sourceParameters}
                targetParams={job?.targetParameters}
                resultParams={job?.resultParameters}
                matchScore={job?.matchScore}
              />
            </section>

            {resultUrl && (
              <ComparisonPlayer
                sourceUrl={sourceFile?.url}
                targetUrl={targetFile?.url}
                resultUrl={resultUrl}
                sourceDuration={sourceFile?.duration}
                targetDuration={targetFile?.duration}
                resultDuration={job?.resultParameters?.duration}
              />
            )}
          </>
        )}
      </div>
    </div>
  );
}
